DEV Community

Cover image for How to Build a GraphQL API with Express and TypeScript
Alahira Jeffrey Calvin
Alahira Jeffrey Calvin

Posted on

How to Build a GraphQL API with Express and TypeScript

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

  1. Create a new directory for your project and navigate to it using your terminal.
  2. Initialize an npm project by running npm init -y.
  3. Install the required dependencies by running the command npm install express express-graphql graphql graphql-tools.
  4. Install the dev dependencies by running npm install -D typescript ts-node @types/node @types/express @types/graphql nodemon.

Configuring Typescript

  1. 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"]
}
Enter fullscreen mode Exit fullscreen mode

Setting up the Project

  1. Create a directory in the root directory of your project and name it src.
  2. 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.
  1. Open the server.ts file and setup the express server as below
import express from "express";

const server = express();

export default server;
Enter fullscreen mode Exit fullscreen mode
  1. 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}`);
});
Enter fullscreen mode Exit fullscreen mode
  1. 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"
}
Enter fullscreen mode Exit fullscreen mode

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.

nodemon setup

Defining the GraphQL schema

  1. 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;
Enter fullscreen mode Exit fullscreen mode

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 type ID, a name field of type String, an age field of type Int, a pictureUri field of type String, and an ownerName field of type String.
  • Next, we created two queries. A getPets query which returns an array of Pet Objects and a getPet query which takes an id as an argument and returns a Pet Object.
  • We also created a createPet mutation which returns the created pet object, a updatePet mutation which returns the updated pet object and a deletePet 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

  1. 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,
};

Enter fullscreen mode Exit fullscreen mode

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 the getPet query and it takes an id argument of type stringand 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 the getPets query.
  • createPet: this is responsible for creating a pet and requires the id, name,pictureUri, ownerName and age which are all string types except for the age argument which is a number type.
  • updatePet: this is responsible for updating a pet and take a required id argument of type string as well as other optional arguments i.e. name, pictureUri, ownerName and age
  • deletePet: this takes a required id argument of type string, 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;

Enter fullscreen mode Exit fullscreen mode

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.

graphiql

  • 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
}}
Enter fullscreen mode Exit fullscreen mode
  • Update a pet
mutation{updatePet(id:"081d0b95-8421-4cb0-8089-c5b8642731ae",name:"lubindos junior",pictureUri:"pictureUri"){
  name,
  ownerName,
  pictureUri,
  age, 
  id
}}

Enter fullscreen mode Exit fullscreen mode
  • Delete a pet
mutation{deletePet(id:"cf31ec68-ada2-46c0-b4a2-9dedd14d2176")}

Enter fullscreen mode Exit fullscreen mode
  • Get a pet
{
  getPet(id:"52599910-16fa-4744-a3fa-7900a8b70185"){
    id,
    age,
    name,
    ownerName,
    pictureUri
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Get all pets
{
  getPets{
    id,
    age,
    name,
    ownerName,
    pictureUri
  }
}
Enter fullscreen mode Exit fullscreen mode

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 (3)

Collapse
 
khemendrabhardwaj profile image
Khemendra-Bhardwaj

express-graphql package is depreciated

Collapse
 
alahirajeffrey profile image
Alahira Jeffrey Calvin • Edited

That post was about a year ago. I'll probably write a new article using the graphql-http package.

Collapse
 
kasir-barati profile image
Mohammad Jawad (Kasir) Barati

Thanks for this post. Some pointers:

  1. use some dynamic typings. I know we can specify something's type using annotations but I would love to see more natural approach. Like when you wanna type your request object in ExpressJS. You import Request interface and that's the end of it.
  2. You could show how import schema from a .graphql file.