Greetings!
In this third article of the GraphQL series, I would like to discuss the steps needed to create a GraphQL server using Node.js with a somewhat more advanced query than the previous one.
The complete code can be found here.
First of all, we need to import graphql from graphql module and graphqlHTTP from express-graphql. Make sure to import methods from the fake database service as well because we are going to access it within this module.
If you look at our database structure, the movie contains directorId. When we fetch a movie, the movie structure has the directorId, not the Director resource. Hence, in the director field, we need to define a resolver for the director.
That's it! Happy learning ☺
https://graphql.org/graphql-js/constructing-types/
In this third article of the GraphQL series, I would like to discuss the steps needed to create a GraphQL server using Node.js with a somewhat more advanced query than the previous one.
The complete code can be found here.
What do we build?
Movie API where we can query movies and directors' information.Initializing the project
npm init -y
npm install express graphql express-graphql --save
npm install @babel/core @babel/node @babel/preset-env --save-dev
npm install nodemon --save-dev
Enable ES6 modules in package.json and add a start command
for nodemon.{
"name": "nodejs-graphql-query",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "nodemon --exec babel-node src/index",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.1",
"express-graphql": "^0.12.0",
"graphql": "^16.6.0"
},
"devDependencies": {
"@babel/core": "^7.19.3",
"@babel/node": "^7.19.1",
"@babel/preset-env": "^7.19.3",
"nodemon": "^2.0.20"
}
}
Create .bablerc and add below to setup
babeltouch .babelrc
{
"presets": ["@babel/preset-env"]
}
Our project structure is like below;
Design the data store
To keep things simple we will use a fake database with an array. The data store has no impact on the GraphQL server. This is our movie-data-service.js module.const movies = [
{ id: "1", title: "The Great Battle", year: 2018, directorId: "2" },
{ id: "2", title: "Baahubali: The Beginning", year: 2015, directorId: "3" },
{ id: "3", title: "Baahubali 2: The Conclusion", year: 2017, directorId: "3" },
{ id: "4", title: "Magadheera", year: 2009, directorId: "3" },
{ id: "5", title: "Rurouni Kenshin Part I: Origins", year: 2012, directorId: "4" },
{ id: "6", title: "Rurouni Kenshin Part II: Kyoto Inferno", year: 2014, directorId: "4" },
{ id: "7", title: "Rurouni Kenshin: The Legend Ends", year: 2014, directorId: "4" },
{ id: "8", title: "Avatar", year: 2009, directorId: "1" },
{ id: "9", title: "Avatar 2", year: 2023, directorId: "1" },
{ id: "10", title: "Titanic", year: 1997, directorId: "1" }
];
const directors = [
{ id: "1", name: "James Cameron" },
{ id: "2", name: "Kwang-shik Kim" },
{ id: "3", name: "S.S. Rajamouli" },
{ id: "4", name: "Keishi Otomo" }
];
export function findMovies() {
return movies;
}
export function findMovie(id) {
return movies.find(movie => movie.id === id);
}
export function findDirector(id) {
return directors.find(director => director.id === id);
}
export function findMoviesByDirector(directorId) {
return movies.filter(movie => movie.directorId === directorId);
}
Express server
Create index.js inside the root directory of the project. We are creating an express server here and separating the GraphQL setup into a separate module. We will implement graphql-schema.js later.import express from "express";
import configureGraphql from "./graphql-schema.js";
const app = express();
configureGraphql(app);
app.listen(3000, () => console.log('Running a GraphQL API server at http://localhost:3000/graphql'));
Design the schema and object types
The first step is to design our object types. It will be a one-to-many relationship between the director and the movie. Note that the "movies" field's type is an array or MovieType.type MovieType {
id: String!
title: String
year: String
director: DirectorType
}
type DirectorType {
id: String!
name: String
movies: [MovieType]
}
GraphQL Schema
Create file graphql-schema.js inside the root directory. We will add GraphQL-related code in this module.First of all, we need to import graphql from graphql module and graphqlHTTP from express-graphql. Make sure to import methods from the fake database service as well because we are going to access it within this module.
import { graphqlHTTP } from "express-graphql";
import graphql from "graphql";
import { findMovie, findMovies, findDirector, findMoviesByDirector } from "./movie-data-service.js";
Instead of defining the
schema using GraphQL schema language, we are constructing the schema
programmatically. We can do that using the GraphQLSchema constructor. In this
approach, we create our Query using GraphQLObjectType. In the programmatic
approach, we define a resolver in our Query type.If you look at our database structure, the movie contains directorId. When we fetch a movie, the movie structure has the directorId, not the Director resource. Hence, in the director field, we need to define a resolver for the director.
resolve: (parent, args) => {}
The
parent is the enclosing parent object which is the movie for the director. We
obtain directorId from the parent/movie and fetch it from the data source. Note
the director field's type is DirectorType.const MovieType = new graphql.GraphQLObjectType({
name: 'MovieType',
fields: () => ({
id: { type: graphql.GraphQLString },
title: { type: graphql.GraphQLString },
year: { type: graphql.GraphQLInt },
director: {
type: DirectorType,
resolve: (parent, args) => {
return findDirector(parent.directorId);
}
}
})
});
Now, let's
look at the DirectorType. As the director directs multiple movies, it is a
collection. Hence we need to use GraphQLList as movies field type. This movies
field parent is director. We fetch movies for the director by using its id.const DirectorType = new graphql.GraphQLObjectType({
name: 'DirectorType',
fields: () => ({
id: { type: graphql.GraphQLString },
name: { type: graphql.GraphQLString },
movies: {
type: new graphql.GraphQLList(MovieType),
resolve: (parent, args) => {
return findMoviesByDirector(parent.id);
}
}
})
});
Root Query
I would like to let my API consumers query;- Query movie by id
- Query director by id
- Query all movies
const RootQueryType = new graphql.GraphQLObjectType({
name: 'RootQueryType',
fields: {
movie: {
type: MovieType,
args: { id: { type: graphql.GraphQLString }},
resolve: (parent, args) => {
return findMovie(args.id);
}
},
director: {
type: DirectorType,
args: { id: { type: graphql.GraphQLString }},
resolve: (parent, args) => {
return findDirector(args.id);
}
},
movies: {
type: new graphql.GraphQLList(MovieType),
resolve: () => {
return findMovies();
}
}
}
});
The next step is to create the schema using GraphQLSchema providing the root query.
const schema = new graphql.GraphQLSchema({ query: RootQueryType });
Finally, we configure the endpoint that exposes
GraphQL schema.function configureGraphql(app) {
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
}
Note that we have exposed graphql UI by giving graphiql: true.
We export the function so that we can pass the express app that we created in index.js.
We export the function so that we can pass the express app that we created in index.js.
export default configureGraphql;
Demo
npm start
We can access our application in the browser at
http://localhost:3000/graphiql.Next steps
Did you notice that when you fetch all movies, there are multiple calls to fetch director information? This is the (N+1) problem in GraphQL. Add a console log into findDirector function and try it. We are going to solve it in our next article.
That's it! Happy learning ☺
References
https://graphql.org/graphql-js/https://graphql.org/graphql-js/constructing-types/
Comments
Post a Comment