Skip to main content

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

Let's write Query and Mutation for Login and Signup for User Service. this is one of the common and primary api that you might need to develop for your application.

Query and Mutation

Login User

In UserResolver file, add the following code,

1@Query((returns) => UserResponse)
2 async loginUser(
3 @Arg("email") email: string,
4 @Arg("password") password: string,
5 @Ctx() ctx: any
6 ): Promise<UserResponse> {
7 const user = await ctx.userModel.findOne({
8 email: email,
9 });
10
11 if (user) {
12 const { err } = await bcrypt.compare(password, user.password);
13
14 if (!!err) {
15 return {
16 success: false,
17 error: "Invalid Credetials",
18 data: null,
19 };
20 } else {
21 return {
22 success: true,
23 error: null,
24 data: user,
25 };
26 }
27 } else {
28 return {
29 success: false,
30 error: "User Not Found",
31 data: null,
32 };
33 }
34 }

On the above code, we are adding a typescript decorator @Query. Also, we add it returns of a custom type here. if a particular query returns a string or array. you can define the default type for it such as string,array or integer.

Sometimes, you need a custom type for your response return type. For example, Here i wanted to return three argument on whether API success or not. they are,

Success , Data and Error

Success indicates whether the API is success or not. Data returns the result data. if the api is failed, it will return the error in error variable.

This is a custom return type. To achieve this, we can create a custom Object Type in type-graphql and use it as a return type here.

create a file UserResponse.ts and add the following code,

1import { Field, ObjectType } from "type-graphql";
2import UserSchema from "./UserSchema";
3@ObjectType({ description: "User Response" })
4export default class UserResponse {
5 @Field(() => Boolean)
6 success: boolean;
7
8 @Field(() => UserSchema)
9 data: UserSchema | null;
10
11 @Field(() => String)
12 error: String | null;
13}

After that, we define the arguments and context inside the login query.

production_blog.png

1async loginUser(
2 @Arg("email") email: string,
3 @Arg("password") password: string,
4 @Ctx() ctx: any
5 ): Promise<UserResponse> { }

Here, we have arguments email and password which is of types string. Also, we define the context which will see in later part of this article.

Inside the login resolver, we check whether email is already exists, if it exists. we check the password and return the user data.

Signup Mutation

In UserResolver, add the following code

1@Mutation(() => UserSchema)
2 async registerUser(
3 @Arg("name") name: string,
4 @Arg("email") email: string,
5 @Arg("password") password: string,
6 @Ctx() ctx: any
7 ): Promise<IUser> {
8 const hashedPassword = await bcrypt.hash(password, 12);
9
10 const user = await new ctx.userModel({
11 name,
12 email,
13 password: hashedPassword,
14 });
15
16 return user.save();
17 }

it will follow the same pattern as login query. only difference is logic inside the function. it creates the hash password and store it in DB.

Dependancy Injection

dependancy injection is a powerful pattern that helps to decouple your application from the business logics.

Let me explain with an example here,

1import UserModel from './userModel'
2
3export class UserResolver {
4
5 @Mutation()
6 async insertData(){
7 const data = UserModel.insertMany(data);
8 return {
9 success : true,
10 data : data,
11 error : null
12 }
13}

In the above example, we import the database model directly in our resolver. There is a downside of doing in that way. let's we are using mongoose as our database. what if you want to change your DB to something else. there you have dependancy inside your business logic.

we can avoid this kind of problem using dependancy injection. there are two ways to achieve dependancy injection in graphql server.

DI using GraphQL Context

First way is using graphql context for dependancy injection in our resolver. pass the mongoose model inside the graphql context.

1import UserModel from './UserModel'
2const server = new ApolloServer({
3 schema,
4 context: () => ({
5 userModel: UserModel,
6 }),
7 });

Now, you can use your mongoose model inside the graphql resolvers from context.

1async loginUser(
2 @Arg("email") email: string,
3 @Arg("password") password: string,
4 @Ctx() ctx: any
5 ): Promise<UserResponse> {
6 const user = await ctx.userModel.findOne({
7 email: email,
8 });
9}

In this simple way, you can make your resolver testable and decoupled from third party dependancies.

DI using Container

Type GraphQL provides a way for DI using container with typedi. In server.ts

1import UserModel from "./UserService/UserModel";
2import { UserResolver } from "./UserService/UserResolver";
3import * as Mongoose from "mongoose";
4import { Container } from "typedi";
5
6Container.set({ id: "USER", factory: () => UserModel });
7
8const schema = await buildSchema({
9 resolvers: [UserResolver],
10 emitSchemaFile: true,
11 nullableByDefault: true,
12 container: Container,
13 });

Here, we set our User Mongoose model inside the container. After that, create a UserService.ts which handles the mongo operations

1import { Service, Inject } from "typedi";
2import { IUserModel, IUser } from "./UserModel";
3
4@Service()
5export class UserService {
6 constructor(@Inject("USER") private readonly user: IUserModel) {}
7
8 async getAll() {
9 return this.user.find();
10 }
11
12 async getById(id: string): Promise<IUser | null> {
13 return this.user.findOne({ _id: id });
14 }
15
16 async getByEmail(email: string): Promise<IUser | null> {
17 return this.user.findOne({ email }).exec();
18 }
19
20 async insertUser(userInfo: any): Promise<IUser> {
21 const user = new this.user(userInfo);
22 return user.save();
23 }
24}

User Service is decorated with @Serviceand user model is inject into it. For every db operations such as

  • getAll
  • getById
  • getByEmail
  • insertUser

we create a function and wrap the Mongodb operation inside it. After that, import the Service inside the resolver constructor and access it using

this.userService.getAll

1constructor(private readonly userService: UserService) {}
2
3@Mutation(() => UserSchema)
4 async registerUser(
5 @Arg("name") name: string,
6 @Arg("email") email: string,
7 @Arg("password") password: string,
8 @Ctx() ctx: any
9 ): Promise<IUser> {
10 const hashedPassword = await bcrypt.hash(password, 12);
11
12 const user = await this.userService.insertUser({
13 name,
14 email,
15 password: hashedPassword,
16 });
17
18 return user;
19 }

Complete Source code

Conclusion

this article covers how to write queries and mutation in typescript graphql server and how to inject dependancy in the graphql resolvers.

Comments

Popular posts from this blog

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...