Learn to Build a GraphQL Express Server with TypeScript

In this article, we will see how to build a GraphQL Express server with Typescript. Learn to Build GraphQL Express Server with TypeScript.

What is GraphQL

Basically, GraphQL is a query language that can be used as an alternative to REST API. Let’s understand this with a simple example.

Now, you want to get a list of projects based on the user id along with user details. In, REST API you can pass the URL as

GET /user/1/projects

GET /user/1/details

What happens if we get more nested data. URLs will become complex for such a request.

To avoid this problem, GraphQL comes into play. you can solve this problem in GraphQL like,

query{
   user(id:1){
     name
     email
     projects{
        id
        name
   }
 }
}

This makes development easier and makes the application more efficient. you can read this article to learn more about GraphQL.

GraphQL Express Server with TypeScript

This article assumes that you have a basic understanding of typescript, you can learn the basics of typescript in this article.

we will learn how to create a CRUD API for users in GraphQL with TypeScript.

Create a project using the command

npm init --yes

After that, install the following dependencies for the project

npm install apollo-server-express reflect-metadata type-graphql class-validator typescript ts-node
  • type-graphql – this library is used to define the type check for Graphql. Type check is available for a lot of libs like express, node, etc.
  • class-validator – it is used to validate the request body input. it will throw an error if the validation fails in GraphQL.
  • typescript – typescript is used to compile the typescript file into javascript.
  • ts-node – ts-node is to compile the file on run time. it is very useful in development.

Once you install all the dependencies, it is time to write some code. create a directory called src. Inside src directory, create a file called server.ts and add the following code,

import { ApolloServer } from 'apollo-server-express';
import * as Express from 'express';
import 'reflect-metadata';
import { buildSchema } from 'type-graphql';

import UserResolver from './resolvers/userResolver';

async function startServer() {
  const schema = await buildSchema({
    resolvers: [UserResolver],
    emitSchemaFile: true
  });

  const app = Express();

  const server = new ApolloServer({
    schema
  });

  server.applyMiddleware({ app });

  app.listen(4000, () =>
    console.log('Server is running on http://localhost:4000/graphql')
  );
}

startServer();

On the above code, we build a graphQL schema using the buildSchema from type-graphql which takes resolvers as an argument.

Further, we initialize the Express server and run the server using the ApolloServer.

Now, it is time to write the resolver, create a resolver directory inside src and add the following code in userResolver.ts

import {
  Arg,
  FieldResolver,
  Query,
  Mutation,
  Resolver,
  Root
} from 'type-graphql';

@Resolver(of => User)
export default class {

  @Query(returns => String)
  hello(): String {
    return 'Welcome';
  }

}

Basically, the resolver will import all the required types from type GraphQL. based on that, we can define the type check for variables.

Here, we have the decorator Resolver which defines that the resolver for User Model. After that, we have defined Query and Mutation with appropriate Decorators. Each decorator can have a return type that validates the function return.

On the above code, we have decorator @Query with a return type of string, if we return anything other than a string, it will throw an error.

Further, we need to write the user Model which can be used as an type check for input and return type in Query and Mutation. Create file User.ts inside schema directory, add the following code,

User Model

import { Field, Int, ObjectType } from 'type-graphql';

@ObjectType()
export default class User {
  @Field(type => Int)
  id: number;

  @Field()
  name: String;

  @Field()
  email: String;
}

The above code defines a User class with Fields declared for it. Here, we have fields such as id, name, and email.

Each field has a type of String, number. it can also be other types as well. Each type can be imported from type-graphql.

Add the User Model inside the Resolver,

import {
  Arg,
  FieldResolver,
  Query,
  Mutation,
  Resolver,
  Root
} from 'type-graphql';
import { UserData, users } from '../data';
import User from '../schemas/User';

@Resolver(of => User)
export default class {
  @Query(returns => String)
  hello(): String {
    return 'Welcome';
  }

  @Query(returns => User, { nullable: true })
  userByID(@Arg('id') id: number): UserData | undefined {
    return users.find(user => user.id === id);
  }

}

As I said earlier, each Query or Mutation can have a return type that validates the function returns. it helps to avoid the bug on compile time itself.

Here, we have Query with a return type of User Model which we defined earlier, function userByID takes the arguments, which has decorator @Arg and name of the argument.

Each variable or function can be defined with the return type, that’s the beauty of TypeScript. After that, we will add the Mutation resolver inside the userResolver.

@Mutation(returns => User)
  createUser(
    @Arg('name') name: String,
    @Arg('email') email: String
  ): UserData[] {
    let id = users.length + 1;
    return users.concat({
      id,
      name,
      email
    });
  }

If you watch it carefully as we did before. we define the Arg here as well. But the Problem is, Let’s say we have a lot of variables inside the request body. We need to define each as a separate argument here.

Instead, we can create a class with an input validator that checks each argument and its type.

Create a class userInput.ts and add the following code

import { Field, InputType } from 'type-graphql';
import { IsEmail, Length } from 'class-validator';
@InputType()
export class UserInput {
  @Field()
  @Length(1, 30)
  name: String;

  @Field()
  @IsEmail()
  @Length(1, 30)
  email: String;
}

import the file inside the userResolver.ts

//import { UserInput } from './userInput';
@Mutation(returns => User)
  createUser(
    @Arg('userInfo') { name, email }: UserInput
  ): UserData[] {
    let id = users.length + 1;
    return users.concat({
      id,
      name,
      email
    });
  }

it will check each input argument and validate it on the userInput.ts. you can add the validation using the class-validator library.

we are using some mock data to test the server. create a file data.ts and add the mock data inside it.

export interface UserData {
  id: number;
  name: String;
  email: String;
}

export const users: UserData[] = [
  { id: 1, name: 'Sample', email: 'sample@gmail.com' },
  { id: 1, name: 'Sample', email: 'sample@gmail.com' }
];

Summary

Final resolver code will look like

import {
  Arg,
  FieldResolver,
  Query,
  Mutation,
  Resolver,
  Root
} from 'type-graphql';
import { UserData, users } from '../data';
import User from '../schemas/User';
import { UserInput } from './userInput';
@Resolver(of => User)
export default class {
  @Query(returns => String)
  hello(): String {
    return 'Welcome';
  }

  @Query(returns => User, { nullable: true })
  userByID(@Arg('id') id: number): UserData | undefined {
    return users.find(user => user.id === id);
  }

  @Mutation(returns => User)
  createUser(
    @Arg('userInfo') { name, email }: UserInput
  ): UserData[] {
    let id = users.length + 1;
    return users.concat({
      id,
      name,
      email
    });
  }
}

It’s time to test the server :-). we can use nodemon to run the development server, you can install it using

npm install nodemon

and add it in the packge.json script

"dev": "nodemon --exec ts-node src/server.ts",

After that, you can run the server using

npm run dev

you can see the output like

and the mutation result is

Learn to Build GraphQL Express Server with TypeScript, Complete Source code can be found here

SHARE ON
Learn to Build a GraphQL Express Server with TypeScript

You May Also Like

Leave a Reply

Your email address will not be published.