Build a basic CRUD backend application in NodeJS with GraphQL & MySQL.
Setup an empty project using Nest CLI.
npm install -g @nestjs/cli nest new nodejs-graphql-sample cd nodejs-graphql-samplePlease use
npmas the default package manager for this tutorial.
For more information, please see NestJS First Steps.
Start the server in watch mode.
npm run start:devGoto http://localhost:3000 You should see the Hello World message.
Install the required packages.
npm install @nestjs/graphql graphql-tools graphql apollo-server-express type-graphqlUpdate the src/app.module.ts as follow.
import { Module } from '@nestjs/common'; import { GraphQLModule } from '@nestjs/graphql'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ imports: [ GraphQLModule.forRoot({ autoSchemaFile: true, }), ], controllers: [AppController], providers: [AppService], }) export class AppModule {}Update the src/main.ts to accept the PORT environment variable.
async function bootstrap() { ... await app.listen(process.env.PORT || 4000); } ...Notes that the default backend port has been changed to
4000as well, to avoid the port conflict with frontend.
nest generate module user nest generate resolver user --no-specThe following folder and files will be created.
src/user/user.resolver.ts src/user/user.module.tsPlease notes that src/app.module.ts also updated automatically as follow.
... import { UserModule } from './user/user.module'; @Module({ imports: [ ... UserModule, ], ... }) export class AppModule {}Create the following file src/user/user.entity.ts.
import { Field, ID, ObjectType } from '@nestjs/graphql'; @ObjectType() export class User { @Field(() => ID) id: string; @Field() name: string; @Field({ nullable: true }) nickName?: string; }Create the following file src/user/create-user.input.ts.
import { Field, InputType } from '@nestjs/graphql'; @InputType() export class CreateUserInput { @Field() name: string; @Field({ nullable: true }) nickName?: string; }Update src/user/user.resolver.ts and add the users query and createUser mutation.
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { CreateUserInput } from './create-user.input'; import { User } from './user.entity'; const users: User[] = []; @Resolver('User') export class UserResolver { @Mutation(() => User) createUser(@Args('input') input: CreateUserInput) { const user = new User(); user.name = input.name; user.nickName = input.nickName; user.id = String(Math.floor(Math.random() * 1000000000)); users.push(user); return user; } @Query(() => [User]) users() { return users; } }just temporary keep the users list in memory, will change to use database later.
Start the server instance in watch mode.
npm run start:devGoto the GraphQL Playground - http://localhost:4000/graphql.
-
Create a new user
mutation { createUser(input: { name: "Tommy" }) { id } }
Output :
{ "data": { "createUser": { "id": "95678594" } } } -
Query the users
query { users { id name } }
Output :
{ "data": { "users": [ { "id": "95678594", "name": "Tommy" } ] } }
Install the required packages.
npm install @nestjs/typeorm typeorm mysql2Add the TypeOrmModule Configuration into src/app.module.ts as follow.
... import { TypeOrmModule } from "@nestjs/typeorm"; import { User } from "./user/user.entity"; ... const databaseUrl = process.env.DATABASE_URL || 'mysql://usr:User12345@localhost:3306/development'; @Module({ imports: [ ... TypeOrmModule.forRoot({ type: 'mysql', url: databaseUrl, database: databaseUrl.split('/').pop(), entities: [User], synchronize: true, logging: true, }), ... ] }) export class AppModule {}Add the TypeOrmModule Configuration into src/user/user.module.ts as follow.
... import { TypeOrmModule } from '@nestjs/typeorm'; import { User } from "./user.entity"; ... @Module({ imports: [TypeOrmModule.forFeature([User])], ... }) export class UserModule {}for more information, please see NestJS Database.
Update the following file src/user/user.entity.ts.
import { Field, ID, ObjectType } from '@nestjs/graphql'; import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @ObjectType() @Entity() export class User { @Field(() => ID) @PrimaryGeneratedColumn('uuid') id: string; @Field() @Column() name: string; @Field({ nullable: true }) @Column({ nullable: true }) nickName?: string; }This is a helper service which using TypeORM's Repository API to access the MySQL database.
nest generate service user --no-spec Update the generated file src/user/user.service.ts as follow.
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateUserInput } from './create-user.input'; import { User } from './user.entity'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} create(input: CreateUserInput) { const user = this.userRepository.create(input); return this.userRepository.save(user); } findOneById(id: string) { return this.userRepository.findOneOrFail(id); } async delete(id: string) { const { affected } = await this.userRepository.delete(id); return affected !== 0; } find() { return this.userRepository.find(); } }Update the following file src/user/user.resolver.ts.
import { NotFoundException } from '@nestjs/common'; import { Args, Mutation, Query, Resolver, ID } from '@nestjs/graphql'; import { CreateUserInput } from './create-user.input'; import { User } from './user.entity'; import { UserService } from './user.service'; @Resolver(User) export class UserResolver { constructor(private readonly userService: UserService) {} @Query(() => User) async user(@Args({ name: 'id', type: () => ID }) id: string) { const user = await this.userService.findOneById(id); if (!user) { throw new NotFoundException(id); } return user; } @Mutation(() => User) createUser(@Args('input') input: CreateUserInput) { return this.userService.create(input); } @Mutation(() => ID, { nullable: true }) async deleteUser(@Args({ name: 'id', type: () => ID }) id: string) { return (await this.userService.delete(id)) ? id : null; } @Query(() => [User]) users() { return this.userService.find(); } }Start a MySQL docker instance.
docker run -d -e "MYSQL_ROOT_PASSWORD=Admin12345" -e "MYSQL_USER=usr" -e "MYSQL_PASSWORD=User12345" -e "MYSQL_DATABASE=development" -p 3306:3306 --name some-mysql bitnami/mysql:5.7.27Start the server instance in watch mode.
npm run start:devGoto the GraphQL Playground - http://localhost:4000/graphql.
-
Create some users
mutation { a: createUser(input: { name: "John" }) { id } b: createUser(input: { name: "Mary" }) { id } }
Output:
{ "data": { "a": { "id": "6ad2b68d-15a8-4e3e-9062-6343324faa7e" }, "b": { "id": "eb777cfa-65d4-4a36-9344-5452284647e6" } } } -
Query the users
query { users { id name } }
Output :
{ "data": { "users": [ { "id": "6ad2b68d-15a8-4e3e-9062-6343324faa7e", "name": "John" }, { "id": "eb777cfa-65d4-4a36-9344-5452284647e6", "name": "Mary" } ] } } -
Delete one of the user by the id
mutation { deleteUser(id: "6ad2b68d-15a8-4e3e-9062-6343324faa7e") }
Output :
{ "data": { "deleteUser": "6ad2b68d-15a8-4e3e-9062-6343324faa7e" } } -
Test the MySQL database
Run the mysql command using the same docker instance.
docker exec -it some-mysql mysql -uroot -p"Admin12345"
Select the data from user table.
mysql> use development; mysql> select * from user; +--------------------------------------+------+----------+ | id | name | nickName | +--------------------------------------+------+----------+ | eb777cfa-65d4-4a36-9344-5452284647e6 | Mary | NULL | +--------------------------------------+------+----------+