在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):kapost/graphql-api-gateway开源软件地址(OpenSource Url):https://github.com/kapost/graphql-api-gateway开源编程语言(OpenSource Language):JavaScript 100.0%开源软件介绍(OpenSource Introduction):This project was open-sourced by Kapost, a content operations and marketing platform developed in beautiful Boulder, CO. We're hiring—join our team! GraphQL API GatewayThis project is an example API Gateway over GraphQL, implemented with Apollo Server 2. The API Gateway here is a service that proxies and resolves requests to join data from different services. In this example, we join data over REST-like JSON APIs from two different example back-end services— This API Gateway implementation shows examples of HTTP caching, error formatting, model organization, defending against bad data, mocking data, and tracking over statsd. It also includes tools for easily mocking portions of queries with different sets of mock data. This allows front-end developers to independently build features while other service APIs are still being developed; and can serve as a predictable, non-changing data set for apps when integration testing. We hope this code can serve as a reference to you to implement some of these features as you become familiar with Apollo Server. This code is provided as-is without some things to be production ready. We note that this example does not include things like query scoring or rate limiting, and assumes being behind a proxy in production as it does not protect against malicious headers or attempt to rate limit. The routing is also hardcoded and would need to be adjusted for real deployment. It does not have an implementation for connections or schema stitching, but these features shouldn't be too difficult to add with the Apollo Server docs. Table of Contents:
Getting StartedYou should have Once installed, running the app is as simple as: yarn install
yarn start This will serve the ENV flags for developmentWe provide a few flags for testing and debugging various aspects of the server.
You can run these by setting the env flag before DEBUG_REQUESTS=1 MOCK_MISSING_RESOLVERS=1 yarn start Building queriesAll GraphQL queries and mutations start from special types called query FetchUserProfile {
currentUser {
name
avatarURL
}
} Similarly, a mutation can be fired in the same way. mutation CreateRandomPlaylist {
createRandomizedPlaylist(title: "My playlist") {
id
title
}
} You should be able to view all available query and mutation fields through the GraphiQL documentation viewer. You may see valid queries in the wild that do not have the {
currentUser {
name
avatarURL
}
}
query {
currentUser {
name
avatarURL
}
} In general, prefer always including both the keyword and name, since we can use the given name to monitor query performance and counts. For most client queries, you will need to query with run-time variables rather than hardcoding values in the query. You can do this by adding typed arguments to the root query and using them within. Ensure you match the type signatures of the query arguments to the field arguments. query CreateRandomPlaylist($title: String!) {
createRandomizedPlaylist(title: $title) {
id
title
}
} The GraphQL Playground has an area for adding query variables as JSON. For an more in-depth guide on writing queries (including other query features), please review the official query GraphQL tutorial. Fetching from clientsThis GraphQL service is served over HTTP, which means there is no requirement to use anything other than HTTP calls. We can ask for GraphQL data simply by using libraries such as Browser / NodeJSThe GraphQL endpoint is served as a
Additionally, you will need to provide authorization in the form of a browser cookie, JWT ( An example with axios: axios.request({
endpoint: "/graphql",
method: "POST",
data: {
query: "query MyQuery {\n...\n}\n",
variables: {
pageSize: 4,
},
},
}) If configured in webpack or other bundlers, you could also import import myQuery from "./queries/myQuery.graphql";
axios.request({
endpoint: "/graphql",
method: "POST",
data: {
query: myQuery,
},
}) RubyJust like JavaScript, you can fire a HTTP POST request with whatever networking solutions you have in your app already. There are also some GraphQL clients that can fire requests with nicer features such as the Error handlingType of errors
Since the GraphQL response undertakes many sub-requests, it can often be more work to parse apart all the various possible error states. Thankfully, a fairly naive approach to all of these errors is usually good enough. From a client's perspective, it often makes sense to bucket these errors into the following categories:
Naively, we can usually escalate missing data to failed. Since GraphQL is strongly typed, this means we will have an errors object. When there are errors, a GraphQL response may look like this. The stacktrace and message will be omitted in production to avoid leaking information to consumers. GraphQL is strict and will cut off data when there are errors in type matching. If there are multiple error objects, you may be able to use a partial response depending on your use case. Remember that a 200 status code for a GraphQL request may still mean there were proxy HTTP errors! Always check for a For most use cases, the following pseudocode is appropriate for bucketing errors in clients for user action:
We recommend extracting this error handing logic into a shared function / package to avoid repeating this same error handling in your clients. Mocking data for developmentAs mentioned above, you can mock out data with the Note that you can also build a "temporary" resolver for new work that simply returns a fixture or some generated object while working on new development. For more info on how mocking works in Exporting entire schemaIf you have a use for the full GraphQL schema, you can export statically with DevelopmentBefore developing, we recommend going through the GraphQL Tutorials on their official site. Key concepts and definitions
Adding and extending schemaAgain, we recommend going through the official schema guide to understand the major language syntax and keywords. At it's core, GraphQL schema is simply a collection of type definitions. There are core, basic types called scalars. Examples of built-in scalars include
GraphQL schemas also require separate type definitions around any GraphQL also includes a specification for directives, which are attributes you can attach to type field. Most common is the Custom scalarsScalars are good for representing "terminal" data in custom shapes—usually we don't want to make objects scalars. For example, we add the Designing Input SchemaGraphQL requires separate Input types for any objects that are passed into field arguments. These types are defined with the In general, try to keep the inputs and outputs as close together as possible but omit denormalized data in the input version. Resolving dataResolvers represent how to get data on schema fields, or the edges of the graph. We define function resolver(obj, args, context, info) {
// return field data in shape of schema
} where
Resolvers can be async to fetch data from external locations (via models) and generally following a pattern similar to the below. async function resolver(obj, args, context, info) {
const data = await context.models.something.get();
return transform(data);
} Apollo-server (the package running the resolution program) will properly halt with async resolvers. You do not need to worry about HTTP errors as they will automatically propagate up to the request runner and are formatted via In a lot of cases, it's good to have resolvers return results in a one-to-one shape with the GraphQL schema. This allows you to use implicit resolver that is provided when no resolver for the By exampleFor example, if you had schema like the following: type UserProfile {
name: String!
avatarURL: String!
} and had a resolver that return an object like the following: // Query.userProfile resolver
function resolver(_obj, _args, _context, _info) {
return { name: "Colorado River", picture: "http://placekitten.com/200/200" };
} you would not need to write a resolver for // UserProfile.avatarURL resolver
function resolver(obj, _args, _context, _info) {
return obj.picture;
} Better yet, you could translate at the node for some fields: // Query.userProfile resolver
import { mapKeys } from "utility/mappers";
function resolver(_obj, _args, _context, _info) {
const result = { name: "Colorado River", picture: "http://placekitten.com/200/200" };
return mapKeys({ name: null, picture: "avatarURL" })(result);
} and then remove the Resolving EnumsWhen resolving an enum, ensure that the string you return from JavaScript matches the enum exactly (capitalized). enum Color {
DARK_BLUE
PUMPKIN_ORANGE
# ...
} function resolver(_obj, _args, _context) {
return "DARK_BLUE";
} Resolving Unions and InterfacesWhen you have a polymorphic type like a union or interface, you must define a special resolver on the type to resolve the actual type. For example, if we had a union like the following: type Song {
# ...
}
type Podcast {
# ...
}
union Audio = Song | Podcast We would need to resolve this with a special type resolver with the following shape (no arguments). function resolveType(obj, context, info) {
return obj.type === "song" ? "Song" : "Podcast";
} The return value must be the type name (properly capitalized). To add to the set of resolvers, you must put this under a // audio/index.js
import resolveType from "./resolveType";
export default {
__resolveType: resolveType,
}; ModelsTo access data from APIs, we use an abstraction called a Model to represent a resource to fetch from another service. Models encapsulate different API calls and are a place to memoize, batch together requests, and return API results in a nice Response objectsDifferent services (and even the same service) end up returning responses in inconsistent shapes. For example, paginated {
"response": [/* ... */],
"page_info": {
page_size: 5,
current: 1,
previous_page: null,
next_page: 2,
total: 21,
total_pages: 5
}
} where Methods on
Properties on
All the response methods are memoized by arguments (meaning the result is stored for any arguments that are deep equals). This means we can When interacting with new services, you should add a subclass if necessary to encapsulate the response. Additionally, for some responses that return arrays via batches, models can map responses for each individual result that still has the pageInfo and headers for consistent access. BatchingThere are frequently cases where resolvers in a list need to fetch from the API but it would be prohibitively expensive to fire an individual request within sub-query resolver. To keep resolvers simple and one-to-one, we use a tool called Typically, you would use data loader instance within a class to batch together const MAX_BATCH_SIZE = 200;
class MyModel {
constructor(connector) {
this.connector = connector;
this.myDataLoader = createDataLoader(this.batchFetch, {
maxBatchSize: MAX_BATCH_SIZE,
});
}
get = (id) => {
return this.myDataLoader.load(ids);
}
getMany = (ids) => {
return this.instanceMemberLoader.loadMany(ids);
}
// private
batchFetch = (ids) => {
return (
this.connector.get("api/something", { params: { ids } })
.then(newClassItems(MyResponse))
.then(map(optional.fromNullable))
);
}
} This will ensure that For more info on DataLoader: Memoizing By ArgumentsIn some models, it may not make sense to use data loader but you may still want to memoize. This helper ensures that for any set of arguments that deep equal, a previous result will be returned if computed already. Example: import memoizeByArgs from "utility/memoizeByArgs";
class Playlist {
constructor(connector |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论