Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/nearform/graphql-auto-federate
Automatically federate a Mercurius GraphQL service
https://github.com/nearform/graphql-auto-federate
federation graphql hacktoberfest mercurius
Last synced: 2 months ago
JSON representation
Automatically federate a Mercurius GraphQL service
- Host: GitHub
- URL: https://github.com/nearform/graphql-auto-federate
- Owner: nearform
- Created: 2022-04-21T16:16:11.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2024-04-08T02:35:45.000Z (10 months ago)
- Last Synced: 2024-04-10T13:12:14.855Z (10 months ago)
- Topics: federation, graphql, hacktoberfest, mercurius
- Language: JavaScript
- Homepage:
- Size: 2.81 MB
- Stars: 32
- Watchers: 53
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# graphql-auto-federate
Automatically federate a GraphQL service
## Quick Start
```js
const fastify = require('fastify')
const mercurius = require('mercurius')
const { buildFederatedService } = require('graphql-auto-federate')const federated = await buildFederatedService({
url: `http://original-service:1234/graphql`
})const federatedService = fastify()
federatedService.register(mercurius, {
...federated,
federationMetadata: true,
jit: 1
})
await federatedService.listen(3001)const gateway = fastify()
gateway.register(mercurius, {
services: [{ name: 'auto', url: `http://localhost:3001/graphql` }],
jit: 1
})
await gateway.listen(3000)// query the gateway @ port 3000
// curl -X POST -H 'content-type: application/json' -d '{ "query": "{ hello(greeting: \"ciao\") }" }' localhost:3000/graphql
```## How it works
Given an existing GraphQL service, `graphql-auto-federate` reads the schema and builds a new service with federation information that acts as a proxy, forwarding requests to the original service:
```txt
( gateway ) --> ( federated "proxy" service ) --> ( original service )
````graphql-auto-federate` discovers as much information as possible from the original service schema, but additional information is typically required for a working federated service.
This can be achieved by specifying `__resolveReference` resolvers and directives for entity types (see [options](#options)).
### \_\_resolveReference
The `__resolveReference` resolver is critical for a working federated service. Implementing `__resolveReference` for entities in this context (a "proxy" to federate an existing service) is not trivial and strongly depends on the schema and entities provided by the original service.
A special `forward` function is provided along with the regular resolver arguments to facilitate querying the original service (errors are already managed).
#### Example
```js
options: {
resolvers: {
User: {
__resolveReference: async (self, args, context, info, forward) => {
const response = await forward({
query: `{ getUser (id: ${self.id}) { name, fullName } }`
})return {
...response.getUser,
...self
}
}
}
}
}
```Note: in some cases, `__resolveReference` is redundant, for example in `Query` resolvers, the original service provides all of the required information without needing to call `__resolveReference` again.
---
## API
### buildFederatedService
`buildFederatedService ({ url, options }) => { schema, resolvers }`
Creates the `{ schema, resolvers }` information to build the federated service.
It performs an `introspection query` to the original service, then augments the schema to produce a federated `schema` and `resolvers`.
`options` should contain additional information for `type` and `resolvers`, that are merged and override those that are discovered.
#### Example
From the original service schema:
```gql
type Query {
getUser(id: ID!): User
getUsers: [User]!
}type Mutation {
createUser(user: InputUser): User
updateUser(id: ID!, user: InputUser): User
deleteUser(id: ID!): ID
}input InputUser {
name: String!
}type User {
id: ID!
name: String!
fullName: String
friends: [User]
}
``````js
const { schema, resolvers } = await buildFederatedService({
url: `http://original-service:1234/graphql`
})
```Generated federated schema is:
```gql
extend type Query {
getUser(id: ID!): User
getUsers: [User]!
}
extend type Mutation {
createUser(user: InputUser): User
updateUser(id: ID!, user: InputUser): User
deleteUser(id: ID!): ID
}
input InputUser {
name: String!
}
type User @key(fields: "id") {
id: ID!
name: String!
fullName: String
friends: [User]
}
```Generated federated resolvers are:
```js
{
Query: {
getUser: (...) => // forward query
getUsers: (...) => // forward query
},
Mutation: {
createUser: (...) => // forward query
updateUser: (...) => // forward query
deleteUser: (...) => // forward query
},
User: {
__resolveReference: (self) => { console.warn('__resolveReference called', self) }
}
}
```#### url
the `url` of the original GraphQL service
#### options
- **auto** (boolean)
`auto` option discovers the schema from the original service and builds the relative federated `schema` and `resolvers` (_default_: `true`)
- **type**
Inject information to the type definition schema, adding `@extend` or `@directives` for entity types. These are merged with (and override) the `auto` discovered ones if any.
- `@extend` (boolean) add "extend" to the type
- `@directives` (string) add directives as a string to the type, see [federation spec](https://www.apollographql.com/docs/federation/federation-spec/#federation-schema-specification) for supported directives#### Example
From original service schema:
```gql
type Query {
getUser(id: ID!): User
getUsers: [User]!
}
type User {
id: ID!
name: String!
fullName: String
}
```Using options:
```js
options: {
auto: false,
type: {
Query: {
'@extend': true
},
User: {
'@directives': '@key(fields: "id") @external'
}
}
}
```Generated federated schema:
```gql
extend type Query {
getUser(id: ID!): User
getUsers: [User]!
}
type User @key(fields: "id") @external {
id: ID!
name: String!
fullName: String
}
```- **resolvers**
Provide `resolvers`, these are merged with (and override) the `auto` discovered ones if any.
---
## Supported features
Automatic generation of federated schema supports
- [x] queries
- [x] mutations
- [x] entities
- [x] scalar types
- [x] enums
- [x] unions
- [x] directives## TODO
- [ ] `options.loaders`
- [ ] headers in `graphqlRequest`
- [ ] improve `__resolveReference` resolution
- [ ] provide fields that need to be resolved (from `context.__currentQuery`?)
- [ ] (from mercurius gateway) do not query `__resolveReference` if not necessary
- [ ] 100% test coverage
- [ ] use a model for `__resolveReference` - `{ query, variables, transform (jsonata) }`
- [ ] more advanced examples in "How it works" section
- [ ] support subscriptions in schema/resolvers
- [ ] comments in federated schema
- [ ] jsdoc and type check
- [ ] expose `buildFederatedInfo` and document it
- [ ] field aliasing on forwarded queries