Build Node, Typescript and GraphQL server from scratch

typescript graphql node express github

A brief tutorial how to create a Node, TypeScript and GraphQL server from scratch with MySQL database. The ouput is a very basic GraphQL server, which you can run, send a simple query to it and get a response. The whole project is available on GitHub.

Requirements:

  • Basic knowledge about JavaScript / TypeScript, Node, GraphQL and SQL
  • Node 12+
  • Database (example is using MySQL but there is no major problem to use any other database)

How is GraphQL query executed

  1. parse the string, throw error if syntax error
  2. validate the query against schema
  3. execute
    1. run query resolver which will return an object
    2. run field resolvers of the specific type by passing there the object from query resolver

Setup new project

Create a folder for your project:

mkdir graphql-example

# cd graphql-example

Install dependencies:

yarn init
yarn add express express-graphql graphql knex mysql2
yarn add --dev @types/node @types/express typescript

# or if you use npm
npm init
npm i express express-graphql graphql knex mysql2
npm i @types/node @types/express typescript --save-dev

Add scripts to package.json:

{
    "name": "graphql-example",
    "scripts": {
        "start": "tsc && node dist/index.js"
    }
}

Create a src folder where the source code will be:

mkdir src

Create src/config.ts file:

export default {
  port: 3010,
  database: {
    type: 'mysql',
    connection: {
      database : '<your database name>',
      host : '<your database host | localhost>',
      password : '',
      user : '',
    },
  },
};

Database layer

Create src/database.ts file where we initialize Knex database client:

import knex from 'knex';
import config from './config';

const database = knex({
  client: 'mysql2',
  ...config.database,
});

export default database;

We will load data from database using Repository Pattern and we start with Author entity. At the beginning it is enough to have just one method to load a specific author. Make directory src/repositories and src/repositories/AuthorRepository.ts file:

import { Author } from '../types';

export default interface AuthorRepository {
  get(id: number): Promise<Author>;
}

Now we need to implement this interface in repository that will use our Knex database client, src/repositories/AuthorKnexRepository.ts:

import AuthorRepository from './AuthorRepository';
import database from '../database';
import { Author } from '../types';

export default class AuthorKnexRepository implements AuthorRepository {

  async get(id: number): Promise<Author> {
    return database.select()
      .from('author')
      .where('id', id)
      .first();
  }
}

GraphQL Schema

GraphQL schema is a something like a definition of what you can use (types, enums, queries, mutations). Because in the first part of this tutorial we will work just with Author, it is enough to have just one type and query.

In src/schema/types/author.ts we define what attributes the Author entity has:

import {
  GraphQLID,
  GraphQLNonNull,
  GraphQLObjectType,
  GraphQLString,
  GraphQLInt,
} from 'graphql';
import { Author } from '../../types';

const author = new GraphQLObjectType({
  name: 'Author',
  fields: () => ({
    id: {
      type: GraphQLNonNull(GraphQLID),
      description: 'Globally unique ID of the author',
      resolve: (obj: Author): string => {
        return Buffer.from(`author-${obj.id}`).toString('base64');
      },
    },
    _id: {
      type: GraphQLNonNull(GraphQLID),
      description: 'Database ID of the author',
      resolve: (obj: Author): number => {
        return obj.id;
      },
    },
    firstName: {
      type: GraphQLNonNull(GraphQLString),
      description: 'Author\'s first name',
      resolve: (obj: Author): string => {
        return obj.firstName;
      },
    },
    lastName: {
      type: GraphQLNonNull(GraphQLString),
      description: 'Author\'s last name',
      resolve: (obj: Author): string => {
        return obj.lastName;
      },
    },
    createdAt: {
      type: GraphQLNonNull(GraphQLString),
      description: '',
      resolve: (obj: Author): string => {
        return obj.createdAt;
      },
    },
  }),
});

export default author;

Here, the most interesting part is probably id where we return Buffer.from(`author-${obj.id}`).toString('base64').

The schema is very simple for now. It contains just one query where we return a specific Author. The author query is in src/schema/queries/author.ts:

import { GraphQLID, GraphQLNonNull } from 'graphql';
import Context from '../../context/Context';
import { default as authorType } from '../types/author';

const author = {
  type: authorType,
  args: {
    id: {
      type: GraphQLNonNull(GraphQLID),
    },
  },
  resolve: (_, { id }, context: Context): Promise<any> => {
    return context.repositories.author.get(id);
  },
};

export default author;

The only remaining step to complete the schema is src/schema/index.ts:

import { GraphQLSchema, GraphQLObjectType } from 'graphql';
import author from './queries/author';

const query = new GraphQLObjectType({
  name: 'Query',
  fields: (): any => ({
    author,
  }),
});

export default new GraphQLSchema({
  query,
});

Express and GraphQL route

In src/index.ts we just connect all those things together and make it accessible through a /graphql route:

import express from 'express';
import graphqlHTTP from 'express-graphql';
import config from './config';
import context from './context';
import schema from './schema';

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.use('/graphql', graphqlHTTP({
  context,
  graphiql: {
    defaultQuery: config.defaultQuery,
  } as undefined,
  schema,
}));

app.listen(config.port, () => {
  console.log(`Server started at http://localhost:${ config.port }`);
});

Try it

If you run the server with yarn start you should be able to visit http://localhost:3010/graphql, where you can find GraphiQL client. Through this, you are able to explore the options of the server - what queries are available and what they return. If you have also mutations or subscriptions, you can see it there too.


Add new comment