Introduction
In a previous post titled GraphQL: An Introduction, we delved into what GraphQL was and touched on how the GraphQL architecture differed from the REST (Representational State Transfer) architecture, various GraphQL concepts as well as situations where it was appropriate to use GraphQL as opposed to REST.
In this post, we are going to learn how to build GraphQL APIs with express and typescript by building a simple barebones API that allows users to share information about their pets.
Prerequisites
Before we begin, make sure you have Node.js and npm (Node Package Manager) installed locally. Additionally, basic knowledge of JavaScript, Node.js, and TypeScript would be beneficial.
Initializing the Project
- Create a new directory for your project and navigate to it using your terminal.
- Initialize an npm project by running
npm init -y
. - Install the required dependencies by running the command
npm install express express-graphql graphql graphql-tools
. - Install the dev dependencies by running
npm install -D typescript ts-node @types/node @types/express @types/graphql nodemon
.
Configuring Typescript
- create a
tsconfig.json
file in the root directory of your project and save the configuration below.
{
"compilerOptions": {
"target": "ES2019",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"outDir": "./dist"
},
"include": ["src"],
"exclude": ["node_modules"]
}
Setting up the Project
- Create a directory in the root directory of your project and name it
src
. - Create five files in the
src
directory and name them:
-
server.ts
: this file would hold and export the code for our Express GraphQL server. -
index.ts
: this file would be responsible for importing and running the Express GraphQL server which would be imported from the./server.ts
file. -
database.ts
: this file would hold our mock database as we would not be using a real database for the sake of simplicity. -
resolvers.ts
: this file would hold the resolvers. Resolvers are functions that are responsible for generating responses for GraphQL queries. -
schema.ts
: This file would hold our GraphQL schema including all the available types, mutations, and queries.
- Open the
server.ts file
and setup the express server as below
import express from "express";
const server = express();
export default server;
- Update the index.ts file with the following code
import server from './server';
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server is running on localhost:${PORT}`);
});
- Open up the package.json which holds all the project's metadata and is located at the root of your project directory and update the scripts section as seen below
"scripts": {
"start": "node dist/index.js",
"start:dev": "nodemon --watch src --exec ts-node src/index.ts"
}
Upon completion of the steps above, running the command npm run start:dev
would start the project using nodemon which would automatically restart the project once changes are made and saved. You should see something like the image below.
Defining the GraphQL schema
- Open the
schema.ts
file you created earlier and type the code below
import { buildSchema } from "graphql";
const schema = buildSchema(`
type Pet {
id: ID!
name: String!
age: Int!
pictureUri: String
ownerName: String!
}
type Query {
getPets: [Pet]
getPet(id: ID!): Pet
}
type Mutation {
createPet(name: String!, age: Int!, pictureUri: String, ownerName: String!): Pet!
updatePet(id: ID!, name: String, age: Int, pictureUri: String, ownerName: String): Pet!
deletePet(id: ID!): ID!
}
`);
export default schema;
Let's break this code down a little bit:
- The schema variable in the code is responsible for holding our GraphQL schema.
- We created a pet schema that has 5 fields. An
id
field of typeID
, aname
field of typeString
, anage
field of typeInt
, apictureUri
field of typeString
, and anownerName
field of typeString
. - Next, we created two queries. A
getPets
query which returns an array ofPet
Objects and agetPet
query which takes anid
as an argument and returns aPet
Object. - We also created a
createPet
mutation which returns the created pet object, aupdatePet
mutation which returns the updated pet object and adeletePet
mutation which returns the id of the deleted pet.
It is important to note that the ! symbol which is used in several fields in the GraphQL schema implies that the field is required.
Creating the resolvers
- Open the
resolvers.ts
file and write the following code
import pets from "./database";
import { randomUUID } from "crypto";
type Pet = {
id: string;
name: string;
age: number;
pictureUri: string;
ownerName: string;
};
const getPet = (args: { id: string }): Pet | undefined => {
return pets.find((pet) => pet.id === args.id);
};
const getPets = (): Pet[] => {
return pets;
};
const createPet = (args: {
name: string;
age: number;
pictureUri: string;
ownerName: string;
}): Pet => {
// generate randon uuid for pet object
const generatedId = randomUUID().toString();
// create pet object and save
const pet = { id: generatedId, ...args };
pets.push(pet);
return pet;
};
const updatePet = (args: {
id: string;
name?: string;
age?: number;
pictureUri?: string;
ownerName?: string;
}): Pet => {
// loop through pets array and get object of pet
const index = pets.findIndex((pet) => pet.id === args.id);
const pet = pets[index];
// update field if it is passed as an argument
if (args.age) pet.age = args.age;
if (args.name) pet.name = args.name;
if (args.pictureUri) pet.pictureUri = args.pictureUri;
return pet;
};
const deletePet = (args: { id: string }): string => {
// loop through pets array and delete pet with id
const index = pets.findIndex((pet) => pet.id === args.id);
if (index !== -1) {
pets.splice(index, 1);
}
return args.id;
};
export const root = {
getPet,
getPets,
createPet,
updatePet,
deletePet,
};
First and foremost, we created a Pet type to enable type checking in typescript. After that, we created five resolvers for the queries and mutations:
-
getPet
: this resolver returns a response for thegetPet
query and it takes anid
argument of typestring
and returns a pet object if it exists. -
getPets
: this resolver gets all the pet objects that are currently in the pets array. It is responsible for returning a response for thegetPets
query. -
createPet
: this is responsible for creating a pet and requires theid
,name
,pictureUri
,ownerName
andage
which are allstring
types except for theage
argument which is a number type. -
updatePet
: this is responsible for updating a pet and take a requiredid
argument of typestring
as well as other optional arguments i.e.name
,pictureUri
,ownerName
andage
-
deletePet
: this takes a requiredid
argument of typestring
, deletes the pet object, and returns the pet id.
Update Code
After writing the schemas and resolvers, the next step is to update the server to make use of the schema as resolvers. Ensure your server.ts
file looks like the one below
import express from "express";
import { graphqlHTTP } from "express-graphql";
import schema from "./schema";
import { root } from "./resolvers";
const server = express();
// setup graphql
server.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
export default server;
Testing the API
If you have reached this stage without problems, congratulations. Now all that's left is for us to test the API. We do this by running the server. Simply type the command npm run start:dev
into your terminal and navigate to http://localhost:3000/graphql
in your browser. You should see the GraphQL playground as shown below.
- Create a pet by typing the code below into the playground
mutation{createPet(name:"lubindos",age: 10, ownerName:"jeffrey",pictureUri:"pictureUri"){
name,
ownerName,
pictureUri,
age,
id
}}
- Update a pet
mutation{updatePet(id:"081d0b95-8421-4cb0-8089-c5b8642731ae",name:"lubindos junior",pictureUri:"pictureUri"){
name,
ownerName,
pictureUri,
age,
id
}}
- Delete a pet
mutation{deletePet(id:"cf31ec68-ada2-46c0-b4a2-9dedd14d2176")}
- Get a pet
{
getPet(id:"52599910-16fa-4744-a3fa-7900a8b70185"){
id,
age,
name,
ownerName,
pictureUri
}
}
- Get all pets
{
getPets{
id,
age,
name,
ownerName,
pictureUri
}
}
NB: Dont forget to edit the fields in the mutation
Conclusion
In this tutorial, we dived a little bit deeper into how to create a GraphQL API with express and typescript by building a simple API. In the next tutorial, we would use the nestjs framework as well as Postgres database and an ORM (Object Relational Mapper) to learn real-world use cases.
Link to code on github: pets-graphql-api
Top comments (2)
express-graphql package is depreciated
That post was about a year ago. I'll probably write a new article using the graphql-http package.