Sometimes your GraphQL server need to use an API that isn't implemented yet or that is unavailable temporarly. Mocking seems the right thing to do in this case but it's hard to maintain good mock data so we end up with "Lorem ipsum" everywhere in our apps.
faker.js
faker.js is a library that let you generate fake data in node or in the browser. It embarks a lot of methods to generate data for common use cases like:
- names
- addresses
- phones
- images
- companies
- ...
Even for... Lorem ipsum!
Other tools
We will use Koa to create the server. It is "a new web framework designed by the team behind Express". In practice, it has a different middleware implementation and embarks less things than express. If you want more information a comparaison is available on Koa's repository.
For GraphQL implementation we will use Apollo Server. It seems to be the most popular server implementation of GraphQL and it fits well with Koa since an apollo-server-koa package exists and have been implemented by the Apollo Server team.
Finally we will use the well-known cross-env package to set environnement variables regardless of the platform you are using.
Setting up the server
First of all, we create a folder with the following structure:
.
├── package.json
└── src
└── index.js
For this article we will be using yarn
but you can use npm
as well. Init package.json with the init
command:
yarn init
Install the following dependencies:
yarn add apollo-server-koa cross-env faker graphql koa
Add a start
script that execute our src/index.js
file:
{
"scripts": {
"start": "node src/index.js"
}
}
In the src/index.js
file we instantiate a new Koa application:
const Koa = require('koa');
const app = new Koa();
app.listen({port: 4000}, () =>
console.log(`🚀 Server ready at http://localhost:4000`),
);
Run your start
script. You should see the console.log
message present in the above example in the console output.
Create the GraphQL endpoint
It is time to implement our GraphQL endpoint. Let's say we are in the situation where an API isn't implemented yet. This API is supposed to expose a list of persons with their firstname and lastname. We will define a schema and make it accessible on a /graphql
endpoint with apollo-server-koa
.
Import ApolloServer
and gql
from apollo-server-koa
:
const {ApolloServer, gql} = require('apollo-server-koa');
We define a query that returns a list of person and the type Person itself:
const typeDefs = gql`
type Person {
lastname: String
firstname: String
}
type Query {
persons: [Person]
}
`;
Since the API doesn't exist yet we throw an error to inform user that he can't use this query:
const resolvers = {
Query: {
persons: () => {
throw Error('Not yet implemented');
},
},
};
We instantiate an Apollo server with the our type definitions and the resolver for our query:
const server = new ApolloServer({
typeDefs,
resolvers,
});
Finally, apply the GraphQL middleware previously created to the Koa application:
server.applyMiddleware({app});
If you start your server right now with the command yarn start
and open the url http://localhost:4000/graphql
in your browser. You should see the nice interface of GraphQL IDE.
If you type a query to retrieve the list of persons with their data:
{
persons {
firstname,
lastname
}
}
It should result in getting the following error:
{
"errors": [
{
"message": "Not yet implemented",
}
]
}
Mock it with faker.js
This error isn't the expected result. We want random realistic data when our server is mocked. To achieve it we need to override the default resolver that throw an error by another that generate fake data.
For this purpose we will set the NODE_ENV
environnement variable to mock
to determine which behavior our Apollo server should follow. We will achieve that by adding a mock
script in our package.json
that set the NODE_ENV
variable using cross-env
and calling the start
script:
{
"scripts": {
"start": "node src/index.js",
"mock": "cross-env-shell NODE_ENV=mock yarn start"
}
}
Apollo server has a mocks
property in its options that take either a Boolean or a object with mocked resolvers. To begin we will set it to true
if NODE_ENV
is equal to mock
:
const server = new ApolloServer({
typeDefs,
resolvers,
mocks: process.env.NODE_ENV === 'mock' ? true : false,
});
At this step if you re-execute your query in the GraphQL IDE you end up with a first result:
{
"data": {
"persons": [
{
"firstname": "Hello World",
"lastname": "Hello World"
},
{
"firstname": "Hello World",
"lastname": "Hello World"
}
]
}
}
That's nice but it isn't really realistic. To add faker we need to implement our own mocked resolver and pass it to Apollo server. In mocked resolvers the Query property has to be a function that return an object with the resolvers definition. In our resolver for persons
query we return an array of two person.
faker.js
methods are organized in namespace. We will use the name
namespace that contains methods such as:
- firstName
- lastName
- jobTitle
- title
- ...
You guess it, we will use firstName
and lastName
methods to generate random data for our two persons:
const mockResolvers = {
Query: () => ({
persons: () => [
{
firstname: name.firstName(),
lastname: name.lastName(),
},
{
firstname: name.firstName(),
lastname: name.lastName(),
},
],
}),
};
// ...
const server = new ApolloServer({
typeDefs,
resolvers,
mocks: process.env.NODE_ENV === 'mock' ? mockResolvers : false,
});
Execute your query in the GraphQL IDE you now have nice datas like:
{
"data": {
"persons": [
{
"firstname": "Mélissa",
"lastname": "Mathieu"
},
{
"firstname": "Camille",
"lastname": "Richard"
}
]
}
}
Bonus
Here is a method I use to generate random sized arrays for my mocked resolvers:
const randomArray = (min, max, callback) => {
const size = random.number({min, max});
return Array.from({length: size}, callback);
};
We could refactor the previous resolver using this method like this:
const mockResolvers = {
Query: () => ({
persons: () =>
randomArray(2, 6, () => ({
firstname: name.firstName(),
lastname: name.lastName(),
})),
}),
};
I made a repository with all the sources presented in this article. Feedback is appreciated 🙏 Please tweet me if you have any questions @YvonnickFrin!