Skip to main content

Build Node, Typescript and GraphQL server from scratch

· 5 min read
Matej Jelluš
Tech leader and IT nerd who is constantly trying new things, sharing his experiences and still enjoys writing code in his free time. Currently looking for new challenges and opportunities.

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
  4. run query resolver which will return an object
  5. 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.

GraphiQL


Do you like this post? Is it helpful? I am always learning and trying new technologies, processes and approaches. When I struggle with something and finally manage to solve it, I share my experience. If you want to support me, please use button below. If you have any questions or comments, please reach me via email juffalow@juffalow.com.

I am also available as a mentor if you need help with your architecture, engineering team or if you are looking for an experienced person to validate your thoughts.