loading...
Cover image for Building NestJS app boilerplate - Authentication, Validation, GraphQL and Prisma

Building NestJS app boilerplate - Authentication, Validation, GraphQL and Prisma

nikitakot profile image Nikita Kot Updated on ・11 min read

The boilerplate app created by this tutorial is here.

⚠️⚠️⚠️ Update - 06 April 2020

NestJS version 7 was recently released. Many thanks to
johnbiundo who posted what changes have to be done for this version update. The github repository is also updated, you can check the changes I've made here.

Intro

NestJS is a relatively new framework in the Node world. Inspired by Angular and built on top of Express with full TypeScript support, it provides a scalable and maintainable architecture to your applications. NestJS also supports GraphQL - a robust query language for APIs with a dedicated, ready to use, @nestjs/graphql module (in fact, the module is just a wrapper around Apollo server).

In this tutorial we're going to build a boilerplate with all the basic features you will need to develop more complex applications. We will use Prisma as a database layer since it works extremely well with GraphQL APIs allowing you to map Prisma resolver to GraphQl API resolvers easily.

By the end of this article we will create a simple blog application, which will allow users to register, log-in and create posts.

Getting Started

NestJS

To start playing with NestJS you should have node (version >= 8.9.0) and npm installed. You can download and install Node from the official website.

After you have node and npm installed, let's install NestJS CLI and initialise a new project.

$ npm i -g @nestjs/cli
$ nest new nestjs-boilerplate
$ cd nestjs-boilerplate

During the installation process you will be asked what package manager you want to use (yarn or npm). In this tutorial I'll be using npm, but if you prefer yarn, go for it.

Now let's run npm start. It will start the application on port 3000, so opening http://localhost:3000 in a browser will display a "Hello World!" message.

GraphQL

As mentioned above, we will use @nestjs/graphql module to setup GraphQL for our API.

$ npm i --save @nestjs/graphql apollo-server-express graphql-tools graphql

After the packages are installed, let's create a configuration file for our GraphQL server.

$ touch src/graphql.options.ts

The configuration will be passed to the underlying Apollo instance by NestJS. A more in depth documentation can be found here.

src/graphql.options.ts

import { GqlModuleOptions, GqlOptionsFactory } from '@nestjs/graphql';
import { Injectable } from '@nestjs/common';
import { join } from 'path';

@Injectable()
export class GraphqlOptions implements GqlOptionsFactory {
  createGqlOptions(): Promise<GqlModuleOptions> | GqlModuleOptions {
    return {
      context: ({ req, res }) => ({ req, res }),
      typePaths: ['./src/*/*.graphql'], // path for gql schema files
      installSubscriptionHandlers: true,
      resolverValidationOptions: {
        requireResolversForResolveType: false,
      },
      definitions: { // will generate .ts types from gql schema files
        path: join(process.cwd(), 'src/graphql.schema.generated.ts'),
        outputAs: 'class',
      },
      debug: true,
      introspection: true,
      playground: true,
      cors: false,
    };
  }
}

Then register GraphQLModule and pass the configuration in application's main AppModule module.

src/app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { GraphqlOptions } from './graphql.options';

@Module({
  imports: [
    GraphQLModule.forRootAsync({
      useClass: GraphqlOptions,
    }),
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

You may have noticed I removed AppController and AppService from the main module. We don't need them since we will be using GraphQL instead of a REST api. The corresponding files can be deleted as well.

To test this setup out, let's create a simple graphql API schema.

$ mkdir src/schema 
$ touch src/schema/gql-api.graphql

src/schema/gql-api.graphql

type Author {
    id: Int!
    firstName: String
    lastName: String
    posts: [Post]
}

type Post {
    id: Int!
    title: String!
    votes: Int
}

type Query {
    author(id: Int!): Author
}

Running npm start will do two things:

  • Generate src/graphql.schema.generated.ts with typescript types which can be used in our source code.
  • Launch the server on port 3000.

We can now navigate to http://localhost:3000/graphql (default GraphQL API path) to see the GraphQL Playground.

graphql playground

Prisma

To run Prisma we need to install Docker, you can follow the installation guide here.

Linux users - you need to install docker-compose separately.

We will be running two containers - one for the actual database and a second one for the prisma service.

Create a docker compose configuration file in the root project directory.

$ touch docker-compose.yml

And put the following configuration there.

docker-compose.yml

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.34
    ports:
      - '4466:4466'
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: postgres
            host: postgres
            port: 5432
            user: prisma
            password: prisma
  postgres:
    image: postgres:10.3
    environment:
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
    volumes:
      - postgres:/var/lib/postgresql/data
volumes:
  postgres: ~

Run docker compose in the root directory of the project. Docker compose will download images and start containers.

$ docker-compose up -d

The Prisma server is now connected to the local Postgres instance and runs on port 4466. Opening http://localhost:4466 in a browser will open the Prisma GraphQL playground.

Now let's install the Prisma CLI and the Prisma client helper library.

$ npm install -g prisma 
$ npm install --save prisma-client-lib

And initialise Prisma in our project root folder.

$ prisma init --endpoint http://localhost:4466

Prisma initialisation will create the datamodel.prisma and prisma.yml files in the root of our project. The datamodel.prisma file contains the database schema and prisma.yml contains the prisma client configurations.

Add the following code to prisma.yml to generate typescript-client so we can query our database.

prisma.yml

endpoint: http://localhost:4466
datamodel: datamodel.prisma
generate:
  - generator: typescript-client
    output: ./generated/prisma-client/

Then run prisma deploy to deploy your service. It will initialise the schema specified in datamodel.prisma and generate the prisma client.

$ prisma deploy

Go to http://localhost:4466/_admin to open the prisma admin tool, a slightly more convenient way to view and edit your data compared to the graphql playground.

Prisma Module

This step is pretty much optional as you can use the generated prisma client as it is in other modules/services etc. but making a prisma module will make it easier to configure or change something in the future.

Let's use the NestJS CLI to create a prisma module and a service. The CLI will automatically create the files boilerplate's files and do the initial module metadata setup for us.

$ nest g module prisma 
$ nest g service prisma

Then let's setup PrismaService.

src/prisma/prisma.service.ts

import { Injectable } from '@nestjs/common';
import { Prisma } from '../../generated/prisma-client';

@Injectable()
export class PrismaService {
  client: Prisma;

  constructor() {
    this.client = new Prisma();
  }
}

And export it in src/prisma/prisma.module.ts.

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

Great! We are done with the initial setup, let's now continue implementing authentication.

Shemas

Database schema

Let's store our boilerplate app schema in database/datamodel.prisma. We can also delete the old datamodel file in the root of the project with default schema.

$ rm datamodel.prisma
$ mkdir database
$ touch database/datamodel.prisma

database/datamodel.prisma

type User {
    id: ID! @id
    email: String! @unique
    password: String!
    post: [Post!]!
    createdAt: DateTime! @createdAt
    updatedAt: DateTime! @updatedAt
}

type Post {
    id: ID! @id
    title: String!
    body: String
    author: User!
    createdAt: DateTime! @createdAt
    updatedAt: DateTime! @updatedAt
}

Then let's modify prisma.yml and define path to our new schema.

prisma.yml

endpoint: http://localhost:4466
datamodel:
  - database/datamodel.prisma
generate:
  - generator: typescript-client
    output: ./generated/prisma-client/

After deploying the schema, the prisma client will be automatically updated and you should see appropriate changes in prisma admin http://localhost:4466/_admin.

$ prisma deploy

API schema

Let's put the following graphql API schema in src/schema/gql-api.graphql.

src/schema/gql-api.graphql

type User {
  id: ID!
  email: String!
  post: [Post!]!
  createdAt: String!
  updatedAt: String!
}

type Post {
  id: ID!
  title: String!
  body: String
  author: User!
}

input SignUpInput {
  email: String!
  password: String!
}

input LoginInput {
  email: String!
  password: String!
}

input PostInput {
  title: String!
  body: String
}

type AuthPayload {
  id: ID!
  email: String!
}

type Query {
  post(id: ID!): Post!
  posts: [Post!]!
}

type Mutation {
  signup(signUpInput: SignUpInput): AuthPayload!
  login(loginInput: LoginInput): AuthPayload!
  createPost(postInput: PostInput): Post!
}

Now launch the app with npm start so it will generate typescript types from the schema above.

Modules

Auth Module

First, we need to install some additional packages to implement passport JWT in our NestJS app.

$ npm install --save @nestjs/passport passport @nestjs/jwt passport-jwt cookie-parser bcryptjs class-validator class-transformer
$ npm install @types/passport-jwt --save-dev

Create AuthModule, AuthService, AuthResolver, JwtStrategy and GqlAuthGuard files.

$ nest g module auth 
$ nest g service auth
$ nest g resolver auth
$ touch src/auth/jwt.strategy.ts
$ touch src/auth/graphql-auth.guard.ts 

src/auth/auth.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User } from '../../generated/prisma-client';

@Injectable()
export class AuthService {
  constructor(private readonly prisma: PrismaService) {}

  async validate({ id }): Promise<User> {
    const user = await this.prisma.client.user({ id });
    if (!user) {
      throw Error('Authenticate validation error');
    }
    return user;
  }
}

The validate method of the auth service will check if a user id from a JWT token is persisted in the database.

src/auth/jwt.strategy.ts

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
import { Request } from 'express';
import { AuthService } from './auth.service';

const cookieExtractor = (req: Request): string | null => {
  let token = null;
  if (req && req.cookies) {
    token = req.cookies.token;
  }
  return token;
};

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: cookieExtractor,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  validate(payload) {
    return this.authService.validate(payload);
  }
}

Here we define where our token should be taken from and how to validate it. We will be passing the JWT secret via environment variable so you will be launching the app with JWT_SECRET=your_secret_here npm run start.

To be able to parse cookies we need to define global cookie-parser middleware.

src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.use(cookieParser());
  await app.listen(3000);
}
bootstrap();

Now let's create a validation class that we will use later and put some email/password validations there.

$ touch src/auth/sign-up-input.dto.ts

src/auth/sign-up-input.dto.ts

import { IsEmail, MinLength } from 'class-validator';
import { SignUpInput } from '../graphql.schema.generated';

export class SignUpInputDto extends SignUpInput {
  @IsEmail()
  readonly email: string;

  @MinLength(6)
  readonly password: string;
}

To make validation work, we need to globally define the validation pipe from @nestjs/common package.

src/app.module.ts

import { Module, ValidationPipe } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { GraphqlOptions } from './graphql.options';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
import { APP_PIPE } from '@nestjs/core';

@Module({
  imports: [
    GraphQLModule.forRootAsync({
      useClass: GraphqlOptions,
    }),
    PrismaModule,
    AuthModule,
  ],
  providers: [
    {
      provide: APP_PIPE,
      useClass: ValidationPipe,
    },
  ],
})
export class AppModule {}

To easily access request and user objects from the graphql context we can create decorators. More info about custom decorators can be found here.

src/shared/decorators/decorators.ts

import { createParamDecorator } from '@nestjs/common';
import { Response } from 'express';
import { User } from '../../../generated/prisma-client';

export const ResGql = createParamDecorator(
  (data, [root, args, ctx, info]): Response => ctx.res,
);

export const GqlUser = createParamDecorator(
  (data, [root, args, ctx, info]): User => ctx.req && ctx.req.user,
);

src/auth/auth.resolver.ts

import * as bcryptjs from 'bcryptjs';
import { Response } from 'express';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { LoginInput } from '../graphql.schema.generated';
import { ResGql } from '../shared/decorators/decorators';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import { SignUpInputDto } from './sign-up-input.dto';

@Resolver('Auth')
export class AuthResolver {
  constructor(
    private readonly jwt: JwtService,
    private readonly prisma: PrismaService,
  ) {}

  @Mutation()
  async login(
    @Args('loginInput') { email, password }: LoginInput,
    @ResGql() res: Response,
  ) {
    const user = await this.prisma.client.user({ email });
    if (!user) {
      throw Error('Email or password incorrect');
    }

    const valid = await bcryptjs.compare(password, user.password);
    if (!valid) {
      throw Error('Email or password incorrect');
    }

    const jwt = this.jwt.sign({ id: user.id });
    res.cookie('token', jwt, { httpOnly: true });

    return user;
  }

  @Mutation()
  async signup(
    @Args('signUpInput') signUpInputDto: SignUpInputDto,
    @ResGql() res: Response,
  ) {
    const emailExists = await this.prisma.client.$exists.user({
      email: signUpInputDto.email,
    });
    if (emailExists) {
      throw Error('Email is already in use');
    }
    const password = await bcryptjs.hash(signUpInputDto.password, 10);

    const user = await this.prisma.client.createUser({ ...signUpInputDto, password });

    const jwt = this.jwt.sign({ id: user.id });
    res.cookie('token', jwt, { httpOnly: true });

    return user;
  }
}

And finally the authentication logic. We are using bcryptjs to hash
and secure out passwords and httpOnly cookie to prevent XSS attacks on
the client side.

If we want to make some endpoints accessible only for signed-up users we need
to create an authentication guard and then use it as a decorator above an endpoint
definition.

src/auth/graphql-auth.guard.ts

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}

Now let's wire up everything in AuthModule.

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthResolver } from './auth.resolver';
import { PrismaModule } from '../prisma/prisma.module';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PrismaModule,
    PassportModule.register({
      defaultStrategy: 'jwt',
    }),
    JwtModule.register({
      secret: process.env.JWT_SECRET,
      signOptions: {
        expiresIn: 3600, // 1 hour
      },
    }),
  ],
  providers: [AuthService, AuthResolver, JwtStrategy],
})
export class AuthModule {}

Cool, authentication is ready! Start the server and try to create a user, log-in and check cookies in a browser.
If you see token cookie everything works as expected.

Post module

Let's add some basic logic to our app. Authorized users will be able
to create posts that will be readable to everyone.

$ nest g module post
$ nest g resolver post
$ touch src/post/post-input.dto.ts

First let's define resolvers for all Post fields and add a simple validation for createPost mutation.

src/post/post-input.dto.ts

import { IsString, MaxLength, MinLength } from 'class-validator';
import { PostInput } from '../graphql.schema.generated';

export class PostInputDto extends PostInput {
  @IsString()
  @MinLength(10)
  @MaxLength(60)
  readonly title: string;
}

src/post/post.resolver.ts

import {
  Args,
  Mutation,
  Parent,
  Query,
  ResolveProperty,
  Resolver,
} from '@nestjs/graphql';
import { PrismaService } from '../prisma/prisma.service';
import { Post } from '../graphql.schema.generated';
import { GqlUser } from '../shared/decorators/decorators';
import { User } from '../../generated/prisma-client';
import { UseGuards } from '@nestjs/common';
import { GqlAuthGuard } from '../auth/graphql-auth.guard';
import { PostInputDto } from './post-input.dto';

@Resolver('Post')
export class PostResolver {
  constructor(private readonly prisma: PrismaService) {}

  @Query()
  async post(@Args('id') id: string) {
    return this.prisma.client.post({ id });
  }

  @Query()
  async posts() {
    return this.prisma.client.posts();
  }

  @ResolveProperty()
  async author(@Parent() { id }: Post) {
    return this.prisma.client.post({ id }).author();
  }

  @Mutation()
  @UseGuards(GqlAuthGuard)
  async createPost(
    @Args('postInput') { title, body }: PostInputDto,
    @GqlUser() user: User,
  ) {
    return this.prisma.client.createPost({
      title,
      body,
      author: { connect: { id: user.id } },
    });
  }
}

And don't forget to define everything in the module.

src/post/post.module.ts

import { Module } from '@nestjs/common';
import { PostResolver } from './post.resolver';
import { PrismaModule } from '../prisma/prisma.module';

@Module({
  providers: [PostResolver],
  imports: [PrismaModule],
})
export class PostModule {}

User Module

Although we don't have any user mutations, we still need to define user resolvers so graphql can resolve our queries correctly.

$ nest g module user 
$ nest g resolver user

src/user/user.resolver.ts

import { Parent, ResolveProperty, Resolver } from '@nestjs/graphql';
import { PrismaService } from '../prisma/prisma.service';
import { User } from '../graphql.schema.generated';

@Resolver('User')
export class UserResolver {
  constructor(private readonly prisma: PrismaService) {}

  @ResolveProperty()
  async post(@Parent() { id }: User) {
    return this.prisma.client.user({ id }).post();
  }
}

And of course UserModule.

src/user/user.module.ts

import { Module } from '@nestjs/common';
import { UserResolver } from './user.resolver';
import { PrismaModule } from '../prisma/prisma.module';

@Module({
  providers: [UserResolver],
  imports: [PrismaModule],
})
export class UserModule {}

Sample Queries

To test your application you can run these simple queries.

Signing-up

mutation {
  signup(signUpInput: { email: "user@email.com", password: "pasword" }) {
    id
    email
  }
}

Logging-in

mutation {
  login(loginInput: { email: "user@email.com", password: "pasword" }) {
    id
    email
  }
}

Creating a post

mutation {
  createPost(postInput: { title: "Post Title", body: "Post Body" }) {
    id
    title
    author {
      id
      email
    }
  }
}

Retrieving all posts

query {
  posts {
    title
    author {
      email
    }
  }
}

Conclusion

We are finally done with our app boilerplate! Check nestjs documentation to add more useful features to your application. When deploying to production environment don't forget to secure your Prisma layer and database.

You can find the final code here.

Posted on by:

nikitakot profile

Nikita Kot

@nikitakot

Software Engineer @ Cimpress || Bassist @ kafka. - https://kafkasteckou.bandcamp.com || Prague, Czech Republic

Discussion

markdown guide
 

Nikita, thanks for the great tutorial. I am running this 1.5 years after you wrote it 😄 and with Nest v7. So there were a couple of issues I had to fix:

Installing graphql@latest (15.0) caused some sort of problem for me. I haven't quite sorted it out, so I fell back to ^14.6.0 to avoid that problem.

With Nest 7, there's a breaking change to createParamDecorator. Here's the change I made to src/shared/decorators/decorators.ts to make it work:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

import { Response } from 'express';
import { User } from '../../../generated/prisma-client';

export const ResGql = createParamDecorator(
  (data: unknown, context: ExecutionContext): Response =>
    GqlExecutionContext.create(context).getContext().res,
);

export const GqlUser = createParamDecorator(
  (data: unknown, context: ExecutionContext): User => {
    const ctx = GqlExecutionContext.create(context).getContext();
    return ctx.req && ctx.req.user;
  },
);
 

Hi John! Thanks for sharing this! I'll update the article and the repo in near future. By the way, there is prisma 2 beta release out, haven't checked it yet, but I'm pretty sure some bigger changes have to be done for the upgrade too.

 

Sounds good. Figured I'd post the code that worked for me as a stop gap in case others run into it.

 

Hi Nikita Gr8 Article ... but Express can't write token'cookie !

this line of code seems ok : "res.cookie('token', jwt, { httpOnly: true });" in auth.resolver.ts,
and the token is correctly generated but no cookie is created in client side...

 

P.S. If you are not using graphql playground and calling the server from a front-end application served from another server (different domain/port/etc.) you need to enable cors on the nestjs server (not described in the article). To do it simply add these lines to your main.ts file to the bootstrap function.

app.enableCors({
    credentials: true,
    origin: true,
  });
 

Cors was the problem yes, thanks again for these speed responses !

 

Please check your graphql playground settings in the browser. You should have "request.credentials": "same-origin" set there to allow CORS.

 
 

That help me a lot, thanks! :D

 

Is this the best approach for log out?

  @Mutation(() => Boolean)
  async logOut(@ResGql() res: Response) {
    res.cookie('token', '', { httpOnly: true })
    return true
  }
 

Hi Elie,

This looks like as the easiest solution. Or you can implement something like token blacklist. More here.

 

Great article Nikita, but authorization doesnt work properly due to problems with cookiest. I can see Set-Cookie header, but it does not add cookie to your browser storage.
github.com/apollographql/apollo-cl...

 

Thanks! About authorisation - take a look at src/main.ts. I'm using external cookieParser library to parse cookies. You sure you also installed it?

 

Nice post. It would be great if you have the same article using Apollo since it is the default graphql server for the nest.

 
 

looks like you didn't generate typescript types from the gql schema, re-check the GraphQL part of the article

 
 

Seems that the Prisma's client generation API changed and it no longer generates the GraphQL schemas. I have the same issue. Any ideas?