2022-11-11 16:30:31 +00:00
|
|
|
---
|
|
|
|
title: Apollo Server
|
|
|
|
categories:
|
|
|
|
- Databases
|
2022-11-19 17:30:05 +00:00
|
|
|
tags: [graphql, REST, APIs]
|
2022-11-11 16:30:31 +00:00
|
|
|
---
|
|
|
|
|
|
|
|
# Apollo Server
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
> Apollo Server is the part of the Apollo suite that we use to create the
|
|
|
|
> backend of a GraphQL project: a GraphQL server.
|
2022-11-11 16:30:31 +00:00
|
|
|
|
|
|
|
It is able to do the following:
|
|
|
|
|
|
|
|
- Receive an incoming GraphQL query from a client
|
|
|
|
- Validate that query against the server schema
|
2022-11-28 13:12:07 +00:00
|
|
|
- Populate the queried schema fields
|
2022-11-16 17:57:44 +00:00
|
|
|
- Return the fields as a JSON response object
|
2022-11-11 16:30:31 +00:00
|
|
|
|
|
|
|
## Example schema
|
|
|
|
|
2022-11-18 19:39:00 +00:00
|
|
|
We will use the following schema in the examples.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2022-11-11 16:30:31 +00:00
|
|
|
```js
|
2022-11-28 13:12:07 +00:00
|
|
|
// schema.js
|
|
|
|
|
2022-11-11 16:30:31 +00:00
|
|
|
const typeDefs = gql`
|
2022-11-18 19:39:00 +00:00
|
|
|
" Our schema types will be nested here
|
2022-11-11 16:30:31 +00:00
|
|
|
`;
|
|
|
|
module.exports = typeDefs;
|
|
|
|
```
|
|
|
|
|
2022-11-18 19:39:00 +00:00
|
|
|
```js
|
|
|
|
type Query {
|
|
|
|
tracksForHome: [Track!]!
|
|
|
|
}
|
|
|
|
|
|
|
|
type Track {
|
|
|
|
id: ID!
|
|
|
|
title: String!
|
|
|
|
author: Author!
|
|
|
|
thumbnail: String
|
|
|
|
length: Int
|
|
|
|
modulesCount: Int
|
|
|
|
}
|
|
|
|
|
|
|
|
type Author {
|
|
|
|
id: ID!
|
|
|
|
name: String!
|
|
|
|
photo: String
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2022-11-11 16:30:31 +00:00
|
|
|
## Setting up the server
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We instantiate an `ApolloServer` instance and pass our schema to it. We then
|
|
|
|
subscribe to it with a
|
|
|
|
[listener](/Programming_Languages/Node/Modules/Core/Node_JS_events_module.md#extending-the-eventemitter-class).
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2022-11-11 16:30:31 +00:00
|
|
|
```js
|
2022-11-28 13:12:07 +00:00
|
|
|
// index.js
|
|
|
|
|
2022-11-11 16:30:31 +00:00
|
|
|
const { ApolloServer } = require("apollo-server");
|
|
|
|
const typeDefs = require("./schema");
|
|
|
|
const server = new ApolloServer({ typeDefs });
|
|
|
|
|
|
|
|
server.listen().then(() => {
|
|
|
|
console.log(`
|
|
|
|
Server is running!
|
|
|
|
Listening on port 4000
|
|
|
|
Query at http://localhost:4000
|
|
|
|
`);
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
When we access the local URL we are able to access the Apollo server using the
|
|
|
|
Explorer GUI. This automatically loads our schema and is basically a more fancy
|
|
|
|
version of GraphiQL:
|
2022-11-11 16:30:31 +00:00
|
|
|
|
2024-02-16 16:14:01 +00:00
|
|
|

|
2022-11-11 16:30:31 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
It makes it easy to read descriptions of the dataypes and to construct queries
|
|
|
|
by clicking to insert fields.
|
2022-11-11 16:30:31 +00:00
|
|
|
|
|
|
|
### Adding some mock data
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We are not connected to a database yet but we can create a mock that will enable
|
|
|
|
us to run test queries.
|
2022-11-11 16:30:31 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We do this just by updating the Apollo Server options. We can either use generic
|
|
|
|
dummy data or provide our own mock.
|
2022-11-11 16:30:31 +00:00
|
|
|
|
|
|
|
#### Generic mock
|
|
|
|
|
|
|
|
```js
|
|
|
|
const server = new ApolloServer({ typeDefs, mocks: true });
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Custom mock
|
|
|
|
|
|
|
|
```js
|
|
|
|
const mocks = {
|
|
|
|
Track: () => ({
|
|
|
|
id: () => "track_01",
|
|
|
|
title: () => "Astro Kitty, Space Explorer",
|
|
|
|
author: () => {
|
|
|
|
return {
|
|
|
|
name: "Grumpy Cat",
|
|
|
|
photo:
|
|
|
|
"https://res.cloudinary.com/dety84pbu/image/upload/v1606816219/kitty-veyron-sm_mctf3c.jpg",
|
|
|
|
};
|
|
|
|
},
|
|
|
|
thumbnail: () =>
|
|
|
|
"https://res.cloudinary.com/dety84pbu/image/upload/v1598465568/nebula_cat_djkt9r.jpg",
|
|
|
|
length: () => 1210,
|
|
|
|
modulesCount: () => 6,
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
const server = new ApolloServer({ typeDefs, mocks });
|
|
|
|
```
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We can now
|
|
|
|
[run queries](/Databases/GraphQL/Apollo/Apollo_Client.md#running-a-query)
|
|
|
|
against our server.
|
2022-11-15 08:02:15 +00:00
|
|
|
|
|
|
|
## Implementing resolvers
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
A resolver is a [function](/Trash/Creating_a_GraphQL_server.md#resolvers) that
|
|
|
|
populates data for a given query. It should have **the same name as the field
|
|
|
|
for the query**. So far we have one query in our schema: `tracksForHome` which
|
|
|
|
returns the tracks to be listed on the home page. We must therefore also name
|
|
|
|
our resolver for this query `tracksForHome`.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
It can fetch data from a single data source or multiple data sources (other
|
|
|
|
servers, databases, REST APIs) and present this as a single integrated resource
|
|
|
|
to the client, matching the shape requested.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
As per the [generic example](/Trash/Creating_a_GraphQL_server.md#resolvers), you
|
|
|
|
write write your resolvers as keys on a `resolvers` object, e.g:
|
2022-11-16 17:57:44 +00:00
|
|
|
|
|
|
|
```js
|
|
|
|
const resolvers = {};
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
The `resolvers` object's keys will correspond to the schema's types and fields.
|
|
|
|
You distinguish resolves which directly correspond to a query in the schema from
|
|
|
|
other resolver types by wraping them in `Query {}`.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
|
|
|
```js
|
|
|
|
const resolvers = {
|
|
|
|
Query: {
|
|
|
|
tracksForHome: () => {},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
### Resolver parameters
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Each resolver function has the same standard parameters that you can invoke when
|
|
|
|
implementing the resolution: `resolverFunction(parent, args, context, info)`.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
|
|
|
- `parent`
|
2024-02-02 15:58:13 +00:00
|
|
|
- Used with
|
|
|
|
[resolver chains](/Databases/GraphQL/Apollo/Using_arguments_with_Apollo_Client.md#resolver-chains)
|
|
|
|
---add example
|
2022-11-16 17:57:44 +00:00
|
|
|
- `args`
|
2024-02-02 15:58:13 +00:00
|
|
|
- an object comprising arguments provided for the given field by the client.
|
|
|
|
For instance if the client requests a field with an accompanying `id`
|
|
|
|
argument, `id` can be parsed via the `args` object
|
2022-11-16 17:57:44 +00:00
|
|
|
- `context`
|
2024-02-02 15:58:13 +00:00
|
|
|
- shared state between different resolvers that contains essential connection
|
|
|
|
parameters such as authentication, a database connection, or a
|
|
|
|
`RESTDataSource` (see below). This will be typically instantiated via a
|
|
|
|
class which is then invoked within the `ApolloServer` instance under the
|
|
|
|
`dataSources` key.
|
2022-11-16 17:57:44 +00:00
|
|
|
- `info`
|
2022-11-28 13:12:07 +00:00
|
|
|
- not used so frequently but employed as part of caching
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Typically you won't use every parameter with every resolver. You can ommit them
|
|
|
|
with `_, __`; the number of dashes indicating the argument placement.
|
2022-11-15 08:02:15 +00:00
|
|
|
|
|
|
|
### `RESTDataSource`
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
A resolver can return data from multiple sources. One of the most common sources
|
|
|
|
is a RESTful endpoint. Apollo provides a specific class for handling REST
|
|
|
|
endpoints in your resolvers: `RESTDataSource`.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
REST APIs fall victim to the "n + 1" problem: say you want to get an array of
|
|
|
|
one resource type, then for each element returned you need to send another
|
|
|
|
request using one of its properties to fetch a related resource.
|
2022-11-15 08:02:15 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
This is implicit in the case of the `Track` type in the schema. Each `Track` has
|
|
|
|
an `author` key but the `Author` type isn't embedded in `Track` it has to be
|
|
|
|
fetched using an `id`. In a REST API, this would require a request to a separate
|
|
|
|
end point for each `Track` returned, increasing the time complexity of the
|
|
|
|
request.
|
2022-11-15 08:02:15 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
Here is an example of `RESTDataSource` being used. It is just a class that can
|
|
|
|
be extended and which provides inbuilt methods for running fetches against a
|
|
|
|
REST API:
|
2022-11-15 08:02:15 +00:00
|
|
|
|
|
|
|
```js
|
2022-11-16 17:57:44 +00:00
|
|
|
const { RESTDataSource } = require("apollo-datasource-rest");
|
2022-11-15 08:02:15 +00:00
|
|
|
|
|
|
|
class TrackAPI extends RESTDataSource {
|
|
|
|
constructor() {
|
|
|
|
super();
|
2022-11-16 17:57:44 +00:00
|
|
|
this.baseURL = "https://odyssey-lift-off-rest-api.herokuapp.com/";
|
2022-11-15 08:02:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getTracksForHome() {
|
2022-11-16 17:57:44 +00:00
|
|
|
return this.get("tracks");
|
2022-11-15 08:02:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getAuthor(authorId) {
|
|
|
|
return this.get(`author/${authorId}`);
|
|
|
|
}
|
|
|
|
}
|
2022-11-16 17:57:44 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
### Using our `RESTDataSource` in our resolver
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
As our GraphQL server is sourcing data from a REST API, we can now integrate the
|
|
|
|
`RESTDataSource` class with our resolver.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
First thing, we need to instantiate an instance of our `TrackApi` class,
|
|
|
|
otherwise we won't be able to use any of its methods in the resolver.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We will create an instance of this class and pass it into `ApolloServer` object
|
|
|
|
we established at the beginning. We will pass it to the `dataSources` key.
|
|
|
|
**This will allow us to access it from within the `context` parameter in our
|
|
|
|
resolver function**
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We can also get rid of the `mocks` object since we don't need it any more. We
|
|
|
|
will replace it with our `resolvers` constant:
|
2022-11-16 17:57:44 +00:00
|
|
|
|
|
|
|
```diff
|
|
|
|
const server = new ApolloServer({
|
|
|
|
typeDefs,
|
|
|
|
- mocks,
|
|
|
|
+ resolvers,
|
|
|
|
+ dataSources: () => {
|
|
|
|
+ return {
|
|
|
|
+ trackApi: new TrackApi()
|
|
|
|
+ }
|
|
|
|
}
|
|
|
|
})
|
|
|
|
```
|
|
|
|
|
|
|
|
Now we can complete our resolver:
|
|
|
|
|
|
|
|
```js
|
|
|
|
const resolvers = {
|
|
|
|
Query: {
|
|
|
|
tracksForHome: (_, __, {dataSources}) => {},
|
|
|
|
return dataSources.trackApi.getTracksForHome()
|
|
|
|
},
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
So we destructure the `dataSources` object from the parent Apollo Server
|
|
|
|
instance (in the place of the `context` parameter) which gives us access to our
|
|
|
|
`trackApi` class. This resolver will now make the API request and return the
|
|
|
|
tracks.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
The `tracksForHome` query returns `Track` objects and these have a required
|
|
|
|
`author` key that returns an `Author` type. So we are also going to need a
|
|
|
|
resolver that can return the author data that will be populated along with
|
|
|
|
`Track`.
|
2022-11-16 17:57:44 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
We already have this functionality in our class: `getAuthor` so we just need to
|
|
|
|
integrate it:
|
2022-11-16 17:57:44 +00:00
|
|
|
|
|
|
|
```js
|
|
|
|
const resolvers = {
|
|
|
|
Query: {
|
|
|
|
tracksForHome: (_, __, { dataSources }) => {
|
|
|
|
return dataSources.trackApi.getTracksForHome();
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Track: {
|
|
|
|
author: ({ authorId }, _, { dataSources }) => {
|
|
|
|
return dataSources.trackApi.getAuthor(authorId);
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
- Just as we nest the `tracksForHome` resolver under `Query`, we must nest
|
|
|
|
`author` under `Track` to match the structure of the schema. This resolver
|
|
|
|
doesn't respond to a query that is exposed to the client so it shouldn't go
|
|
|
|
under `Query`.
|
2022-11-28 13:12:07 +00:00
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
* We invoke the `context` again when we destructure `dataSources` from the
|
|
|
|
`ApolloServer` instance.
|
|
|
|
* This time we utilise the `args` parameter in the resolver since an `id` will
|
|
|
|
be provided as a client-side
|
|
|
|
[argument](/Databases/GraphQL/Apollo/Using_arguments_with_Apollo_Client.md) to
|
|
|
|
return a specific author.
|
2022-11-28 13:12:07 +00:00
|
|
|
|
|
|
|
## The `useMutation` hook
|
|
|
|
|
|
|
|
We invoke the `useMutation` hook to issue mutations from the client-side.
|
|
|
|
|
2024-02-02 15:58:13 +00:00
|
|
|
As with queries and
|
|
|
|
[query constants](/Databases/GraphQL/Apollo/Apollo_Client.md#query-constants)
|