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:
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 : '',
},
},
};
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 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,
});
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 }`);
});
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.