Building a Production-grade Nodejs,GraphQL and TypeScript Server with CI/CD Pipeline - Part 1

Building a Production-grade Nodejs,GraphQL and TypeScript Server with CI/CD Pipeline - Part 1

This article is the first part of building a production grade nodejs,graphql and typescript server with auto deployment pipeline.

Recent articles

there are tons of articles and tutorials are available to explain how to build a scalable backend solutions. but, what i felt missing was a solution that connects all the dots with a simple and proper use-cases.

Most of the time, we won't directly jump into building the application by coding it. we would start with designing how the backend should be based on the application requirement. knowing how to do that is an important skill to have than just knowing the syntax of x or y framework.

What are we building?

Before coding, let's see what we are going to build to learn the concepts. we will be building a Image management application where users can manage their image resources.

image_hub.png

Mainly, it will contain login,signup and main application. In main application, user can upload images and view all the uploaded images. Even though it's a simple use-case, we can understand all the concepts using this simple use-case.

Screens

Signup Screen

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/Untitled.png

Login Screen

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/Untitled%201.png

Main App Screen

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/Untitled%202.png

Checkout the Figma file Here

Now, we know what application we are going to build. let's apply all the best practices to build a Nodejs,GraphQL and TypeScript Server.

This part is going to explain how to setup the application and create a query and mutations using GraphQL and TypeScript.

Setup

we will be using type-graphql for your application. let's install the required dependancies for our application,

1npm i apollo-server-express express graphql reflect-metadata type-graphql mongoose
  • Apollo-server-express - It handles the Apollo graphql and Express connetion. apollo server connects with express for request handling.
  • express- expressjs handles the requests in the server
  • graphql - it is used to handle the graphql schema and resolvers.
  • type-graphql - This library is used to manage all the types in graphql.
  • mongoose - library used to connect with mongodb database
1npm i --save-dev @types/express @types/graphql @types/node nodemon ts-node ts-node-dev typescript

Above dependancies helps us to manage the types and development server daemon for our application.

Now, we need to configure tsconfig file in our application. create a file tsconfig.json and add the following,

1{
2 "compilerOptions": {
3 "target": "es6",
4 "module": "commonjs",
5 "lib": ["dom", "es6", "es2017", "esnext.asynciterable"],
6 "sourceMap": true,
7 "outDir": "./dist",
8 "moduleResolution": "node",
9 "declaration": false,
10 "composite": false,
11 "removeComments": true,
12 "noImplicitAny": true,
13 "strictNullChecks": true,
14 "strictFunctionTypes": true,
15 "noImplicitThis": true,
16 "noUnusedLocals": true,
17 "noUnusedParameters": true,
18 "noImplicitReturns": true,
19 "noFallthroughCasesInSwitch": true,
20 "allowSyntheticDefaultImports": false,
21 "emitDecoratorMetadata": true,
22 "experimentalDecorators": true,
23 "skipLibCheck": true,
24 "baseUrl": ".",
25 "rootDir": "src"
26 },
27 "exclude": ["node_modules"],
28 "include": ["./src/**/*.tsx", "./src/**/*.ts"]
29}

create a file src/server.ts and add the following code,

1import { ApolloServer } from "apollo-server-express"
2import * as Express from "express"
3import "reflect-metadata"
4import { buildSchema } from "type-graphql"
5
6import * as Mongoose from "mongoose"
7async function startServer() {
8 require("dotenv").config(__dirname + ".env")
9
10 const schema = await buildSchema({
11 resolvers: [],
12 emitSchemaFile: true,
13 })
14
15 const app = Express()
16
17 const MONGO_USER = process.env.MONGODB_USER
18 const MONGO_PASS = process.env.MONGODB_PASS
19
20 Mongoose.connect(
21 `mongodb+srv://${MONGO_USER}:${MONGO_PASS}@cluster0-xv4mh.mongodb.net/test?retryWrites=true&w=majority`,
22 {
23 useNewUrlParser: true,
24 useUnifiedTopology: true,
25 }
26 )
27 .then(res => {
28 console.log("Mongodb is connected successfully")
29
30 const server = new ApolloServer({
31 schema,
32 context: () => ({}),
33 })
34
35 server.applyMiddleware({ app })
36 const PORT = process.env.PORT
37 app.listen(PORT, () => {
38 console.log(`server is running on PORT ${PORT}`)
39 })
40 })
41 .catch(err => {
42 console.log(err)
43 })
44}
45
46startServer()

On the above code, we create a async function to bootstrap our application.

1async function startServer() {
2 //Main code comes Here
3}
4
5startServer()

Inside the function, we build the schema for your application by providing the resolvers. yeah, you read it right.. Graphql schemas are auto generated in type-graphql.

1const schema = await buildSchema({
2 resolvers: [//our resolvers come here],
3 emitSchemaFile: true,
4 });

After that, we connect with MongoDB Database and start our serve.

1Mongoose.connect(
2 `mongodb+srv://${MONGO_USER}:${MONGO_PASS}@cluster0-xv4mh.mongodb.net/test?retryWrites=true&w=majority`,
3 {
4 useNewUrlParser: true,
5 useUnifiedTopology: true,
6 }
7)
8 .then(res => {
9 console.log("Mongodb is connected successfully")
10
11 const server = new ApolloServer({
12 schema,
13 context: () => ({}),
14 })
15
16 server.applyMiddleware({ app })
17 const PORT = process.env.PORT
18 app.listen(PORT, () => {
19 console.log(`server is running on PORT ${PORT}`)
20 })
21 })
22 .catch(err => {
23 console.log(err)
24 })

Environment Variable Setup

we will be using dotenv in our application to manage the environment variables for development.

1require("dotenv").config(__dirname + ".env")

.env

1PORT=5678
2MONGODB_USER=root
3MONGODB_PASS=Root!23456

Now, we got everything to run the basic server in typescript. To run the development typescript server, add the following script in application.

1"dev": "ts-node-dev --respawn src/server.ts"

you can run your development server using the command,

1npm run dev

DDD for our Application

Let's break our application into domains. so, that we can wrap our business logic based on the domains.

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/ddd.png

Here, we can consider the entities as our feature. we have User and Asset as an entity. Let's create the file structure based on that,

Let's start with User Service and create all the Queries and Mutations for that service.

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/Screenshot_2020-04-26_at_7.48.26_PM.png

MongoDB and TypeScript

To work with MongoDB and Typescript, let's install few dependancies that are required.

1npm i --save-dev @types/mongoose

After that, create a UserModel.ts file in the UserService and add the following code.

1import * as Mongoose from "mongoose"
2
3export interface IUser extends Mongoose.Document {
4 name: String
5 email: String
6 password: String
7}
8
9const UserSchema: Mongoose.Schema = new Mongoose.Schema(
10 {
11 name: {
12 type: String,
13 required: true,
14 },
15 email: {
16 type: String,
17 required: true,
18 },
19 password: {
20 type: String,
21 required: true,
22 },
23 },
24 { timestamps: true }
25)
26
27export default Mongoose.model<IUser>("User", UserSchema)

On the above code, we define a mongoose schema which returns type mongoose schema along with the fields for User Model.

After that, we define an interface for our User Model,

1export interface IUser extends Mongoose.Document {
2 name: String
3 email: String
4 password: String
5}

If you are new to typescript interfaces, checkout this docs to know more.

1export default Mongoose.model<IUser>("User", UserSchema)

Finally, we export the mongoose model which implements IUser interface. we have successfully create our mongoose model.

Note: Here i am using MongoDB as database. if you want to use SQL, checkout this series from ben awad

Resolvers

Every resolver in type-graphql starts with an Resolver decorator tag with it.

1@Resolver(of => UserSchema)
2export class UserResolver {
3 //Queries and Mutation Comes Here
4}

Here, we define it is a resolver of a particular Schema. Let's write the schema file for user and continue with resolver function.

UserSchema.ts

1import { Field, ObjectType, ID } from "type-graphql"
2import { IsEmail, Length } from "class-validator"
3
4@ObjectType({ description: "User Schema" })
5export default class User {
6 @Field(() => ID)
7 id: String
8
9 @Field()
10 @Length(1, 30)
11 name: String
12
13 @Field()
14 @IsEmail()
15 @Length(1, 30)
16 email: String
17
18 password: String
19}

On the above code, we define it is an Object type with a decorator. For every field, we have a Field decorator. So that, it will reflects in the graphql schema. we can also validate different parameters such as length and email.

If you watch it carefully, we haven't set any decorators for password. if you don't want a value to be in the graphql schema. we have left it out from Field decorator. import the schema in our resolver.

1import UserSchema from "./UserSchema"

Let's write a sample Query and test it out whether our setup is working correctly.

1@Query(() => String)
2 sample(): String {
3 return "Hello";
4 }

Every Query and Mutation starts with Query or Mutation Decorator and it's return type. Here, we have a return type of String.

UserResolver.ts

1import {
2 Arg,
3 FieldResolver,
4 Query,
5 Mutation,
6 Resolver,
7 Ctx,
8 Root,
9} from "type-graphql"
10import UserSchema from "./UserSchema"
11
12@Resolver(of => UserSchema)
13export class UserResolver {
14 @Query(() => String)
15 sample(): String {
16 return "Hello"
17 }
18}

After that, import the resolver in our server file and define it in our schema resolvers

1import { UserResolver } from "./UserService/UserResolver"
2
3const schema = await buildSchema({
4 resolvers: [UserResolver],
5 emitSchemaFile: true,
6})

Building%20a%20Production%20grade%20Nodejs%20GraphQL%20and%20Typ/Screenshot_2020-04-27_at_7.55.50_AM.png

Now, we have a Typescript GraphQL Server running successfully in our machine.

https://giphy.com/embed/102h4wsmCG2s12

Conclusion

I am wrapping up this part here, Let's see how to create

  • Login
  • Register
  • Authentication
  • File Upload

functionalities in upcoming articles. Until then Happy coding

To Read More

How to build an Actionable data ta...

In this article, we will see how to build an Actionable data table using a react...

How to Integrate Google Sheet in No...

This article explains how to Integrated Google sheet with your Nodejs Applicatio...

Kubernetes for Nodejs developers

Do you keep hearing the word kubernetes in the tech community and you couldn't u...