During this standard, we will create a Heroes graphQL API. We will have a Hero model with superheroes real and hero names. We will add one example of association.
Our API will be lightly protected and use batching to minimise DB round-trips.
Steps (~61min)
Note: You should commit between each step.
Initialise a new project (~6min)
Create and go to a new directory for the project: mkdir graphql_formation && cd graphql_formation
Init a git repository: git init
Create two services with Docker-compose, one postgres database and one node server:
For this step, notice that our final folder architecture looks like this:
CHECK 1: You terminal should prompt the logs of the two containers together with two different colors
CHECK 2: From another terminal, you can access the API and see the following folder structure: docker-compose exec api /bin/sh then inside the container: ls -lath;
drwxrwxr-x 3 node node 4.0K Aug 17 12:37 .
-rw-rw-r-- 1 node node 0 Aug 17 12:37 index.js
drwxrwxr-x 222 node node 12.0K Aug 17 12:37 node_modules
-rw-rw-r-- 1 node node 426 Aug 17 12:37 package.json
-rw-rw-r-- 1 node node 66.2K Aug 17 12:37 yarn.lock
-rw-rw-r-- 1 node node 86 Aug 17 12:32 Dockerfile
drwxr-xr-x 3 root root 4.0K Aug 3 11:50 ..
Exit with: CTRL-D
CHECK 3: You can access the db and prompt the PostgreSQL version: docker-compose exec db psql -U heroesuser -d heroesdb then inside the container: select version();
PostgreSQL 9.6.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit
Exit with: CTRL-D
Create a koa server (~3min)
Install koa and koa-router in our API: cd api && yarn add koa koa-router
In the index.js file, create our server:
import Koa from 'koa';
import koaRouter from 'koa-router';
const app = new Koa();
const router = new koaRouter();
router.get('/', ctx => {
ctx.body = 'Hello World!';
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(3000);
console.log('Server is up and running');
CHECK 1: In your terminal which run docker-compose, you should see Server is up and running
CHECK 2: Hitting localhost:3000 should return Hello World!: curl localhost:3000
Create a presentation layer with graphQL (~6min)
This layer will let our API know how to present data: what data one user can query? How should front end query this data (fields, root queries, sub queries...)?
Install graphQL, graphQL Server Koa, graphQL tools and Koa body-parser: yarn add graphql graphql-server-koa graphql-tools koa-bodyparser
In a new folder api/presentation add a new schema.js file describing a simple graphQL schema:
import koaBody from 'koa-bodyparser';
import { graphqlKoa } from 'graphql-server-koa';
import schema from './presentation/schema';
...
router.post(
'/api',
graphqlKoa(async ctx => {
return {
schema: schema,
context: {},
debug: true,
};
})
);
...
// Write the following line before all other app.use(...) calls:
app.use(koaBody());
CHECK 1: In Postman, making a POST request to localhost:3000/api which content-type is JSON(application/json) with the following raw body:
{
"query": "{heroes { firstName lastName }}"
}
Install Koa graphiQL: yarn add koa-graphiql
In the index.js file, let our API knows it should use Koa-graphiql:
import graphiql from 'koa-graphiql';
...
// Write the following block after others router.verb(...) calls:
router.get('/graphiql', graphiql(async (ctx) => ({
url: '/api',
})));
CHECK 2: Hitting localhost:3000/graphiql should return graphiql interface and show the Docs
CHECK 3: Using graphiql interface with the following query:
{
heroes {
firstName
lastName
}
}
...should return our two heroes, Clark and Bruce:
Create a business layer (~5min)
In a new api/business folder add a new hero.js file describing our class for this business object:
Run the migration and the seed: yarn knex migrate:latest && yarn knex seed:run
CHECK 1: You can access the db and prompt content of the Heroes table: docker-compose exec db psql -U heroesuser -d heroesdb then inside the container: select * from "Heroes";;
id | firstName | lastName | heroName
----+-----------+----------------+-----------------
1 | Clark | Kent | Superman
2 | Bruce | Wayne | Batman
3 | Peter | Parker | Spiderman
4 | Susan | Storm-Richards | Invisible Woman
(4 rows)
Exit with: CTRL-D
Create a db layer with knex (~6min)
This layer let our API query the data using knex query builder.
Install knex and pg in our API: cd api && yarn add knex pg
CHECK 1: In Postman, making a POST request to localhost:3000/api which content-type is JSON(application/json) with the following raw body:
{
"query": "{hero(id:1) { id firstName lastName heroName }}"
}
...should return UNAUTHORIZED.
CHECK 2: In Postman, making a POST request to localhost:3000/api which content-type is JSON(application/json) with the following raw body:
{
"query": "{heroes { id firstName lastName heroName }}"
}
...should return UNAUTHORIZED.
CHECK 3: In Postman, making a POST request to localhost:3000/api which content-type is JSON(application/json) and Authorization Header is Bearer authorized with the following raw body:
Troubleshooting: Accessing data by id in the correct order (~5min)
You should notice that in Postman making a POST request to localhost:3000/api which content-type is JSON(application/json) and Authorization Header is Bearer authorized with the following raw body:
In our db layer, modify api/db/queryBuilders/hero.js like this:
+import { orderByArgIdsOrder } from '../../utils';
class Hero {
static async getByIds(ids: Array<number>): Promise<Array<CostDBType>> {
return db
.select()
.table('Heroes')
+ .whereIn('id', ids)
+ .orderByRaw(orderByArgIdsOrder(ids));
}
CHECK 1: In Postman, making a POST request to localhost:3000/api which content-type is JSON(application/json) and Authorization Header is Bearer authorized with the following raw body:
...should return Batman (as h1) then Superman (as h2).
Next steps
...should return our two heroes, Clark and Bruce:
This layer will contain all business logic: access controll, scoping / whitelisting, batching and caching and computed properties. More explanations can be found . In this MO, we will only cover access control logic and batching / caching.
This is a very simple example, for a more advanced solution, prefer using .
Add
Add graphiQL with authorization header (get inspired by )