- Notifications
You must be signed in to change notification settings - Fork4
segpacto/gql-gateway
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This module provides a GraphQL Gateway that allows the interaction with Swagger based REST APIs, by autogenerating and merging their GraphQL schemas. 🚀
Through this gateway, it is possible to easily establish aggregations between the downstream REST services using GraphQL generated types, queries and mutations.
- Read and parse the Swagger specifications from all given endpoints.
- For each Swagger specification auto-generate the GraphQL Types, Queries and Mutations; as well as auto-generate the APIs based resolvers.
- Merge our local GraphQl definitions containing the aggregations and extensions along with the previous generated schemas.
- Serve an Apollo GraphQl server with all agreggations.
npminstall--savegql-gateway
constgateway=require('gql-gateway')constendpointsList=[{name:'petstore_service',url:'https://petstore.swagger.io/v2/swagger.json'},{name:'fruits_service',url:'https://api.predic8.de/shop/swagger'}]gateway({ endpointsList}).then(server=>server.listen(5000)).then(console.log('Service is now running at port: 5000')).catch(err=>console.log(err))
constgateway=require('gql-gateway')constlocalSchema=` extend type Order { pet: Pet }`constresolvers={/* Query : { .... } */Order:{pet:{fragment:'... on Order {petId}',asyncresolve(order,args,context,info){constschema=awaitcontext.resolveSchema('pet_service')returninfo.mergeInfo.delegateToSchema({ schema,operation:'query',fieldName:'getPetById',args:{petId:order.petId}, context, info})}}}}constconfig={port:5000,playgroundBasePath:'gql-gateway'}constendpointsList=[{name:'pet_service',url:'https://petstore.swagger.io/v2/swagger.json'}]constapolloServerConfig={playground:{endpoint:config.playgroundBasePath}}gateway({ resolvers, localSchema, endpointsList, apolloServerConfig}).then(server=>server.listen(config.port)).then(console.log(`Service is now running at port:${config.port}`)).catch(err=>console.log(err))
What just happened?
- On
localSchema
we declare the aggregations that we would like to have by extending the original schemas (to get the original schemas, queries and mutations it is recommended to publish the service and then take a look at them before start adding aggregations). - On
resolvers
we declare the way how to resolve the modelOrder
, for this we use graphqldelegations
, where we specify on which of the autogenerated queries or mutations we relay to obtain thepet
property inOrder
, in this casegetPetById
.
Note that on the fragment part we declare
petId
as required field to obtain thepet
property, sopetId
is going to be injected from theOrder
to the resolver even if it haven't been requested originally.
Name | Default | Description |
---|---|---|
localSchema | empty | Schema that contains the aggregations that we want to establish between the REST API services |
resolvers | empty | Resolvers that implement delegation. See samples above |
endpointsList | required | Contains a list ofjson swagger endpoints where to retrieve definitions to build the graphql schemas.Minimum one element |
apolloServerConfig | empty | Apollo Server configuration (https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserver) |
contextConfig | empty | Object that contains middlewares and also used to inject data into the Context (https://www.apollographql.com/docs/apollo-server/api/apollo-server/#apolloserver) |
logger | console | Default logger is the console |
Name | Default | Description |
---|---|---|
name | required | Is used to identify the service |
url | required | Url of the service swagger injson format |
headers | empty | Headers passed to request the json swagger service, in case any kind of particular auth is needed |
onLoaded | empty | Function that process the swaggerJson once is loaded so changes on the flight can be introduced. If passedMust return the swaggerJson back |
Name | Default | Description |
---|---|---|
swaggerJson | Swagger JSON schema | Contains the loaded Swagger Json schema |
service | object | Contains thelocalSchema that was loaded |
onLoaded
function
Ex :
constonLoaded=(swaggerJson,service)=>{swaggerJson.schemes=['http','https']returnswaggerJson}constendpointsList=[{name:'pet_service',url:'https://petstore.swagger.io/v2/swagger.json', onLoaded}]
constapolloServerConfig={playground:{endpoint:config.playgroundBasePath}}
Below, we describe how to interact between services swagger based using agreggations(relations).
In this example we take theUser
andProduct
services as example.
...paths: "/users": get: description: "Return an Array of existing users" responses: '200': description: "successful operation" schema: type: array items: "$ref": "#/definitions/User"...definitions : User: type: object properties: userId: type: string firstname: type: string lastname: type: string...
...paths: paths: '/products/{userId}': get: tags: - Product parameters: - name: userId in: path description: ID of the user to fetch last products required: true type: string summary: Return a summary of the last products description: Return a sumary of the user products responses: '200': description: successful operation schema: type: array items: "$ref": "#/definitions/Product"...definitions : Product: type: object properties: productId: type: string userId: type: string name: type: string type: type: string...
Once the graphql gateway read from those services their swagger specification, our server generates the following:
type Queries { get_products_userId(userId: String!): Products! get_users(): [User]!}
The next step is to extend the GraphQL definitions to introduce our custom global aggregations:
- Extend
GraphQl
Types definitions:
extend type User { products: Product } # You can always declare the relation in one direction extend type Product { user: User }
This will automatically indicate to the GraphQl server that the Type
User
will have another field named products, actually theProduct
service relation.
- Finally, extend
GraphQl
resolvers:
User:{products:{fragment:'... on User {userId}',asyncresolve(user,args,context,info){returninfo.mergeInfo.delegateToSchema({schema:userSchema,operation:'query',fieldName:'get_products_userId',args:{userId:user.userId// here we hook the relation identifier}, context, info})}}},
- And now we can magically query:
query { get_users { firstname, lastname, products { name, type } }}