Products
chatkit_full-logo

Extensible API for in-app chat

channels_full-logo

Build scalable realtime features

beams_full-logo

Programmatic push notifications

Developers

Docs

Read the docs to learn how to use our products

Tutorials

Explore our tutorials to build apps with Pusher products

Support

Reach out to our support team for help and advice

Sign in
Sign up

Building a mobile chat app with Nest.js and Ionic 4 - Part 1: Build the backend

  • Ahmed Bouchefra
December 10th, 2018
You should have Node 8.11+ installed on your machine. Some familiarity with TypeScript will be helpful.

Introduction

In this first part of this tutorial series we will learn how to build a fullstack mobile application using cutting edge technologies like Ionic 4, Nest.js and Pusher Chatkit. Ionic will handle our front end, we will build the backend of the app using Nest.js and use Pusher Chatkit to handle messaging.

The application that you’ll be building is a group chat application that will allow users to register, login and then chat with a group of other users.

The aim of this tutorial is to show you how you can use Pusher’s Chatkit to easily add chat features in your mobile applications built with Ionic 4, Angular and Nest.js.

You can find the source code for the first part from this GitHub repository.

Since we are not going to create the mobile UI in this part, we’ll be using cURL to interact with our application. This is a GIF image that shows how to send to POST request to the /register endpoint to register a user in then send a POST request to the /login endpoint to get a JWT token:

Prerequisites

You need to have a basic understanding of TypeScript and Node.js to follow this tutorial. TypeScript is a superset of JavaScript that adds static types to the language.

Both Nest.js and Ionic 4 (based on Angular) requires TypeScript so you need to be familiar with it.

You also need to have a recent version of Node.js (v8.11.2) and NPM (v5.6.0) installed on your machine, if they are not installed on your system, you simply need to head to the official website and grab the binaries for your system or refer to your operating system instructions for installing Node.js via the official package manager of your system.

Why use Chatkit?

Chatkit is a Pusher hosted API that allows developers to build apps with chat features without re-inventing the wheel. The available features include:

  • Group chat
  • One-to-one chat
  • Private chat
  • Typing indicators
  • "Who's online" presence
  • Read receipts
  • Photo, video, and audio messages

The set of features covers the most needed chat features in most apps, which means you can focus on building the features that are specific to your app and let Pusher take care of the commonly needed chat features including managing chat state and data, scaling and infrastructure.

Configuring Chatkit

Setting up Chatkit is quite easy, first you need to have an account with Pusher, next, simply head to the dashboard. Under the CHATKIT box, click on the CREATE button to create your own Chatkit instance:

Enter a name for your instance and click on CREATE:

After creating your Chatkit instance, head to the Credentials tab and take note of your Instance Locator and Secret Key. You will need them later to connect your application to your Chatkit instance.

You also need to manually create a room where we'll add new users once they sign up in our application but to interact with the instance you first need to create a user:

Add the required information and create you first Chatkit user:

You'll be taken to this interface:

Under User Actions, click on Create and join a room. Give a name to your room and click on CREATE ROOM:

Take note of the room ID because we'll need it later in this tutorial series.

Note: Please note that except for this first user that we manually created, all the other users will be created from the SDK and added to the room when new users register via our application interface.

Chatkit handles all the chat data and features but we need a server to create users and add authentication. For this matter, we'll use Nest.js for setting up an authentication server.

Installing the Nest.js CLI

Before creating a Nest.js project we first need to install Nest.js CLI which makes it easy to create and manage Nest.js projects. The CLI helps you from the first step of creating a project to the final step of building a production version of your final app. It's based on the @angular-devkit package and provides its own schematics for Nest.js development which is @nestjs/schematics.

You can install Nest.js CLI from npm via the following command:

    $ npm install -g @nestjs/cli

Note: Please note that you may need to use sudo on Debian based system or macOS or an elevated administrator command prompt on Windows to install Node.js globally on your system depending on your npm configuration. As the time of this writing, Nest.js CLI v5.6.3 will be installed.

You can also create a Nest.js project by pulling the nestjs/cli[:version] Docker image or cloning the https://github.com/nestjs/nest-cli.git repository and installing dependencies. For more information, you can see the official repository.

Creating a new Nest.js project

After installing Nest.js CLI, you can now run the following command to easily create a new project:

    $ mkdir chatkit-nestjs-ionic
    $ cd chatkit-nestjs-ionic
    $ nest new server

The CLI will ask you for a bunch of information like the description and author and which package manager to use for installing packages, either npm or Yarn, enter the required information then hit Enter to start generating your project files and installing dependencies:

Wait a little to finish the installation process:

Then you can navigate inside your project's folder and run a local development server:

    $ cd server
    $ npm run start

As you can see from the screenshot, this command allows you to start a development server on the port configured inside the src/main.ts file.

Your server is now running, you can simply open your browser and navigate to localhost:3000. You should see the Hello world! message.

Since we use Chatkit for adding all chat features, we will not need to implement any feature in the server except for JWT authentication and user management.

Setting up TypeORM and creating a database

For storing and registering users we need a database.

Nest.js supports TypeORM which is considered the most mature Object Relational Mapper (ORM) available in TypeScript. It's available from the @nestjs/typeorm package.

Let's start by installing the required dependencies:

    $ npm install --save @nestjs/typeorm typeorm sqlite3

For the sake of simplicity, we'll use an SQLite database, but TypeORM supports all major databases like MySQL, PostgreSQL, MSSQL, Oracle, and MongoDB.

Note: Since an ORM abstracts away any direct operation with the underlying database system, you can later switch to use a fully fledged system like MySQL for production without changing anything in your code. But for now, let's keep it simple and use SQLite.

After finishing up with installing the dependencies, you need to import the TypeOrmModule into the root ApplicationModule module. Open the src/app.module.ts file and add the following changes:

    // server/src/app.module.ts
    import { Module } from '@nestjs/common';
    import { TypeOrmModule } from '@nestjs/typeorm';

    import { AppController } from './app.controller';
    import { AppService } from './app.service';

    @Module({
      imports: [
       TypeOrmModule.forRoot({
          type: 'sqlite',
          database: 'my.db',
          entities: [__dirname + '/**/*.entity{.ts,.js}'],
          synchronize: true,
       }),
    ],
      controllers: [AppController],
      providers: [AppService],
    })
    export class AppModule {}

We import TypeOrmModule and we call the forRoot() method which takes the same configuration object as the standard createConnection() method of TypeORM.

In the configuration object, we specify:

  • The sqlite string for type so we can use SQLite as the database,
  • The my.db string for the database file (SQLite uses files to store the database),
  • The entities array which refers to all files that end with .entity.ts or .entity.js extensions. These files are created by developers and contain the ORM entities.
  • The synchronize option which takes true or false and allows you to automatically sync your database tables with the entities each time you run the app. In development, you can set it to true but it's not preferable in production.

Note: Now, you can inject the Connection and EntityManager services anywhere you want to access them.

Next, let's create a User entity which corresponds to a user in the database. Create a src/models/user.entity.ts file and add the following class:

    // server/src/models/user.entity.ts
    import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
    @Entity()
    export class User {
      @PrimaryGeneratedColumn()
      id: number;

      @Column()
      name: string;

      @Column()
      email: string;

      @Column()
      password: string;
    }

You need to import the User entity and add it in the imports array of the module using the forFeature method:

    // server/src/app.module.ts
    import { User } from './models/user.entity';
    ...

    @Module({
    imports: [
    ...
    TypeOrmModule.forFeature([User]),

Next, let's create a UserService that encapsulates all database operations that we need to perform against the User model.

Head back to your terminal and run the following command to generate a service:

    $ nest g s user

This command will create the src/user/user.service.ts file that contains the actual service code and the src/user/user.service.spec.ts file that contains the unit tests for the service. And also update the src/app.module.ts file by including UserService in the providers array.

Next, let's add the create and findByEmail methods in the src/user/user.service.ts file which will be used respectively to persist a user and find a user by its email in the database.

    // server/src/user/user.service.ts
    import { Injectable } from '@nestjs/common';
    import { User } from '../models/user.entity';
    import { Repository } from 'typeorm';
    import { InjectRepository } from '@nestjs/typeorm';

    @Injectable()
    export class UserService {
        constructor(
            @InjectRepository(User)
            private userRepository: Repository<User>,
        ) { }

        async  findByEmail(email: string): Promise<User> {
            return await this.userRepository.findOne({
                where: {
                    email: email,
                }
            });
        }

        async  create(user: User): Promise<User> {
            return await this.userRepository.save(user);
        }
    }

First we import User, Repository and InjectRepository, next, inject the User repository via the service's constructor and finally we define our methods.

The findByEmail method simply calls the findOne method of the injected repository to search for a user by the passed email in the database.

The create method calls the save method of the injected repository to save a user in the database.

Adding JWT authentication

Authentication is important for most web applications. You can follow different ways and approaches to implement user authentication. In this tutorial, we'll implement authentication with JSON Web Tokens (JWTs).

First, you need to install the JWT utilities module for Nest.js using :

    $ npm install --save @nestjs/jwt

Next, open the /src/app.module.ts file and include the module in the imports array:

    // server/src/app.module.ts
    import { JwtModule } from  '@nestjs/jwt';
    // [...]

    JwtModule.register({
        secretOrPrivateKey:  'secret123'
    })

We also provided a private secret key that will be used to sign the JWT payload.

To interact with Chatkit, you also need valid JWT tokens that will be obtained by the client by using a token provider and will be sent with every request that the client makes to Chatkit.

Chatkit provides a test token provider that can be used to quickly start testing the chat features but it should be only used for testing. For production, you need to create your own token provider which can be done in two ways:

  • Either, by using the provided server SDKs.
  • Or without the help of the server SDKs using a JWT library or your own custom JWT implementation. See this link for more information.

In this tutorial, we'll use the Node.js SDK for Chatkit to add a token provider in our Nest.js project so head back to your terminal and run the following command from the root of your project to install it:

    $ npm install @pusher/chatkit-server --save

Next, let's create the AuthService class that will encapsulate the code for implementing JWT authentication in our application.

Using Nest.js CLI run the following command to generate a service:

    $ nest g s auth

This command will add the /src/auth/auth.service.ts file that contains the service and the /src/auth/auth.service.spec.ts file that contains the tests for the service and will update the main app module contained in the /src/app.module.ts file to include the generated service.

If you open the main module file at this stage, you can see that the JwtauthService was imported and included in the providers array:

    // server/src/app.module.ts
    import { Module } from '@nestjs/common';
    import { AppController } from './app.controller';
    import { AppService } from './app.service';
    import { AuthService } from './auth/auth.service';
    // [...]

    @Module({
      imports: [/* [...] */],
      controllers: [AppController],
      providers: [AppService, UserService,AuthService],
    })
    export class AppModule {}

Now, after creating the service, you need to import the Chatkit server SDK, JwtService , UserService , the User entity and the AuthenticationResponse. Open the src/auth/auth.service.ts file and add the following import:

    // server/src/auth/auth.service.ts
    import Chatkit from '@pusher/chatkit-server';
    import { JwtService } from  '@nestjs/jwt';
    import { UserService } from  '../user/user.service';
    import { User } from  '../models/user.entity';
    import  Chatkit, { AuthenticationResponse } from  '@pusher/chatkit-server';

Next, you need to add the following code:

    // server/src/auth/auth.service.ts
    @Injectable()
    export class AuthService {
      chatkit: Chatkit;
      constructor(
        private readonly userService: UserService,
        private readonly jwtService: JwtService
      ) {
        this.chatkit = new Chatkit({
          instanceLocator: YOUR_INSTANCE_LOCATOR,
          key: YOUR_SECRET_KEY
        })    
      }

We add a member variable to the service that holds the Chatkit instance. Next we inject UserService and JwtService via the constructor and inside it, we create the Chatkit instance.

Replace YOUR_INSTANCE_LOCATOR and YOUR_SECRET_KEY with the credentials from the dashboard. When a user connects to Chatkit, a request will be sent to a /token endpoint (that will be created later in this tutorial) to authenticate the user. Your server has to send a response that contains a token using the Chatkit.authenticate method if the request is valid.

Now, you need to define and implement the following methods:

  • getToken: It's used to create and return a valid JWT token. This method will simply use the authenticate method of the Chatkit instance to generate a valid token.
  • validateUser: It's used to validate the user. This method will use the findByEmail method of UserService to check if the user exists in the database.
  • createUser: It's used to create a user in the local database and then in the Chatkit instance.

Let's start with the createUser method which takes a parameter of the User type:

    // server/src/auth/auth.service.ts
    private async createUser(userData: User): Promise<User>{
        return this.userService.create(userData).then(user =>{
          const userId = `${user.name}${user.id}`;
          const roomId = "YOUR_ROOM_ID";
          const avatarURL = "https://image.flaticon.com/icons/png/128/149/149071.png";

          return this.chatkit.createUser({id: userId, 
             name: user.name,
             avatarURL: avatarURL
          }).then(()=>{

            return this.chatkit.addUsersToRoom({ roomId: roomId,
              userIds: [userId]}).then(()=>{
                return user;
            });

          })

        });
    }

Replace YOUR_ROOM_ID with the room id from the dashboard.

This method calls the create method of UserService to persist the user in the database then when the Promise successfully resolves with a user object that has a unique identifier in the database we use the id and name to create a corresponding user in the Chatkit instance by calling the createUser method of the instance and finally we add the user to the room by calling the addUsersToRoom method.

The createUser method of the Chatkit instance requires a unique user identifier and a user name. We construct the user id by concatenating the name with the database id of the user. This way we make sure the Chatkit user id is unique. We also provide a user avatar for testing using the https://image.flaticon.com/icons/png/128/149/149071.png URL.

Note: In a production application, you need to provide your users with a way to upload their avatars then associate them with the Chatkit user. You also need to hash passwords before storing them in the database using a tool like bcrypt.

Let's now define the getToken method. It takes a user ID and returns an AuthenticationResponse:

    // server/src/auth/auth.service.ts
    public getToken(userId:  string): AuthenticationResponse {
        return this.chatkit.authenticate({ userId: userId });
    }  

The getToken method is simply a wrapper around the authenticate method of the Chatkit instance which returns a valid JWT token that can be used by the client to access Chatkit APIs. The authenticate method takes a userId that we specify when we create the user in the Chatkit instance (a concatenation of the word name and the database identifier of the user).

Another method that we need to define is the validateUser method which takes a parameter of the User type:

    // server/src/auth/auth.service.ts
    private async validateUser(userData: User): Promise<User> {
        return await this.userService.findByEmail(userData.email);
    }

This method calls the findByEmail method of UserService which checks if the user with the email exists in the database. If it exists the user object is returned otherwise a null object is returned.

After defining these methods, we'll use them to define two public methods in the same service which are:

  • register for registering users,
  • login for login users.

This is the implementation of the two methods:

    // server/src/auth/auth.service.ts
    public async login(user: User): Promise<any | {status: number}>{
        return this.validateUser(user).then((userInfo)=>{
          if(!userInfo){
            return { status: 404 };
          }
          let userId = `${userInfo.name}${userInfo.id}`;
          const accessToken = this.jwtService.sign(userId);
          return {
             expires_in: 3600,
             access_token: accessToken,
             user_id: userId,
             status: 200
          };

        });
    }

    public async register(user: User): Promise<any>{
        return this.createUser(user)
    }

In the login method, we first use the validateUser method to make sure the user exists in the database then we call the sign method of JwtService to create an access token from the user id and name payload. Finally, we return an object containing the expires_in, access_token, user_id and status properties.

In the register method, we simply call the previously-defined createUser method to create a user in the database and then in the remote Chatkit instance.

Creating endpoints

After implementing the login and register methods, it's time to create the corresponding endpoints in our application that handle user authentication. We also need to create a /token endpoint that will be used by the Chatkit client SDK to request JWT tokens from our server.

Open the existing src/app.controller.ts file and update it accordingly:

    // server/src/app.controller.ts
    import { Post, Body,Request, Controller} from '@nestjs/common';
    import { AuthService } from './auth/auth.service';
    import { User } from './models/user.entity';

    @Controller()
    export class AppController {
      constructor(private readonly authService: AuthService) {}

      @Post('token')
      async token(@Request() req): Promise<any> {
        return this.authService.getToken(req.query.user_id).body;
      }

      @Post('login')
      async login(@Body() userData: User): Promise<any> {
        return this.authService.login(userData);
      }  

      @Post('register')
      async register(@Body() userData: User): Promise<any> {
        return this.authService.register(userData);
      }    
    }

We start by importing the Post, Request and Body decorators, and also AuthService and the User entity. Next, we inject AuthService as an authService instance via the controller's constructor.

Finally, we instruct Nest.js to create the three /token, /login and /register routes that accept a POST request by decorating their methods with the @Post decorator (the route is passed as a parameter).

For the login and register methods, we use the @Body() decorator to instruct Nest.js to inject the body of the received request in the endpoint handler as userData.

For the token method we need the full request so we use the @Request decorator instead.

Note: We could also create a controller for handling authentication using nest g controller auth but since our Nest.js app has only one task which is to handle JWT auth we can simply use the existing application controller.

Testing our auth endpoints

After creating the authentication endpoints, let's use cURL to test them before we create our front-end mobile application in the next tutorial.

First, run the following command from the root of your project to start the Nest.js development server:

    $ npm start 

Next, make sure you have cURL installed on your system and run the following command from your terminal:

    curl -X POST -H 'content-type: application/json'  -d  '{ "email": "ahmed@gmail.com", "name": "ahmed", "password": "pass001" }' localhost:3000/register

This will create a user in your SQLite database and a Chatkit user that you can see from the Console/INSTANCE INSPECTOR tab in your Chatkit dashboard. The endpoint returns the created Chatkit user with the id, name, created_at and updated_at fields.

You can also test the /login endpoint using:

    curl -X POST -H 'content-type: application/json'  -d  '{ "email": "ahmed@gmail.com", "password": "pass001"}' localhost:3000/login

This should return a response object with an access token and a user id.

Enabling CORS

Since we'll be using Ionic for creating the mobile app that will interact with this server and we'll do most Ionic development on the browser we need to setup CORS (Cross Origin Resource Sharing). Otherwise, the browsers will block the requests to the server due to the same origin policy.

You can easily enable CORS in Nest.js by opening the src/main.ts file and calling the app.enableCors method:

    // server/src/main.ts
    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';

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

Conclusion

In this tutorial, we've seen how to create a server for JWT authentication using Nest.js and Chatkit's SDK for Node.js.

In the next tutorial, we'll continue developing our mobile application that uses this server for authentication and Chatkit for implementing the chat features.

You can find the source code for the first part from this GitHub repository.

Clone the project repository
  • Android
  • Angular
  • Chat
  • Cordova
  • iOS
  • JavaScript
  • TypeScript
  • Chatkit

Products

  • Channels
  • Chatkit
  • Beams

© 2019 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.