This article was originally published at https://www.blog.duomly.com/node-js-course-with-building-a-fintech-banking-app-lesson-2-user-registration
In the previous week, I've published the first lesson of the Node.js Course, where we started a project using Nest.js, Nest CLI, PostgreSQL database, and Sequelize. Besides that, we managed to create migrations and set up the database.
So, if you would like to be updated, feel free to go back to the lesson one and follow up or get the first lesson code from our Github.
Also, if you would like to compare backend created in Node.js with backend created with GoLang, then check out my friend's Golang Course. Both are created along with the Angular 9 Course and Python and AI Course. All of them are used to build one fintech application.
Today I'm going to show you how to create user registration in Node.js.
We are going to create two modules, Users and Accounts, and we will build the functionality for creating a new user, and each new user will have a new account assigned.
And of course, as always, we've got a video version for you!
Let's start!
1. Refactor migrations
The first step will be to refactor the migrations we did in the last lesson. We have to add a few columns to the tables. So, let's run npm run migrate down
twice to drop both tables. Open 1.1users.ts
file and make the following changes in the code.
import * as Sequelize from 'sequelize';
const tableName = 'Users';
export async function up(i: any) {
const queryInterface = i.getQueryInterface() as Sequelize.QueryInterface;
queryInterface.createTable(tableName, {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
unique: true,
primaryKey: true,
},
Username: {
type: Sequelize.CHAR(200),
allowNull: false,
},
Email: {
type: Sequelize.CHAR(50),
allowNull: false,
},
Password: {
type: Sequelize.CHAR(250),
allowNull: false,
},
Salt: {
type: Sequelize.CHAR(250),
allowNull: true,
},
createdAt: {
type: Sequelize.DATE,
},
updatedAt: {
type: Sequelize.DATE,
}
});
};
export async function down(i: any) {
const queryInterface = i.getQueryInterface() as Sequelize.QueryInterface;
queryInterface.dropTable(tableName);
}
Now, open the other migrations file 1.2accounts.ts
and make sure it looks like in the code below.
import * as Sequelize from 'sequelize';
const tableName = 'Accounts';
export async function up(i: any) {
const queryInterface = i.getQueryInterface() as Sequelize.QueryInterface;
queryInterface.createTable(tableName, {
id: {
type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true,
unique: true,
primaryKey: true,
},
Type: {
type: Sequelize.CHAR(200),
allowNull: false,
},
Name: {
type: Sequelize.CHAR(200),
allowNull: false,
},
Balance: {
type: Sequelize.INTEGER,
allowNull: true,
},
UserId: {
type: Sequelize.INTEGER,
references: {
model: 'Users',
key: 'id',
},
},
createdAt: {
type: Sequelize.DATE,
},
updatedAt: {
type: Sequelize.DATE,
}
});
};
export async function down(i: any) {
const queryInterface = i.getQueryInterface() as Sequelize.QueryInterface;
queryInterface.dropTable(tableName);
}
The last step now is to rerun migrations, so let's use npm run migrate up
and check if your database changed.
2. Install packages
To create user registration, we need some additional packages. Let's open the console and install jsonwebtoken
.
$ npm install jsonwebtoken
And the other package we need is dotenv
, to create the configuration.
$ npm install dotenv
When it's done, let's go to the next step.
3. Create .env file
Go to the root file, create a new file, and call it .env
. To this file, we are going to move database configuration.
DB_HOST=<YOUR_HOST>
DB_USER=<YOUR_USERNAME>
DB_PASS=<YOUR_PASSWORD>
DB_NAME=<YOUR_DB_NAME>
JWT_KEY=<YOUR_JWT_KEY>
Now, we need to require this configuration in the main.ts
file, and later, let's change the database configuration in the migrate.ts
and database.provider.ts
files.
Let's start from main.ts
and import the .env
.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
require('dotenv').config()
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Now, open migrate.ts
and make sure it looks like this.
...
require('dotenv').config()
const sequelize = new Sequelize({
dialect: 'postgres',
host: process.env.DB_HOST,
port: 5432,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
});
...
And finally, open the database.provider.ts
file.
export const databaseProvider = [
{
provide: 'SEQUELIZE',
useFactory: async () => {
const sequelize = new Sequelize({
dialect: 'postgres',
host: process.env.DB_HOST,
port: 5432,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
});
sequelize.addModels([Users, Accounts]);
return sequelize;
}
}
]
In the next step, we will create a JWT config.
4. JWT config
Let's go to the src
folder and create a new folder called config
. In that config folder, we will create a jwtConfig.ts
file, and let's set options for our jwt.
export const jwtConfig = {
algorithm: 'HS256',
expiresIn: '1 day',
}
Another necessary thing, we need to do right now is to generate a JWT_KEY and add it to the .env
.
You can generate the key using any of the tools available on the internet or using the console command:
ssh-keygen -t rsa -b 2048 -f jwtRS256.key
When it's ready and added to the .env
file, let's got to the next step!
5. User module and entity
In this step, we are going to create a user.module.ts
and we can do it manually or using Nest CLI.
$ nest generate module modules/user
Right now, we don't need to do anything else in this file, so let's create the next file in the user folder, users.entity.ts
. And inside this file, we are going to set the data we will pass to the database.
import { Table, Column, Model, DataType, CreatedAt, UpdatedAt, HasMany } from 'sequelize-typescript';
import { TableOptions } from 'sequelize-typescript';
const tableOptions: TableOptions = { timestamp: true, tableName: 'Users' } as TableOptions;
@Table(tableOptions)
export class Users extends Model<Users> {
@Column({
type: DataType.INTEGER,
allowNull: false,
autoIncrement: true,
unique: true,
primaryKey: true,
})
public id: number;
@Column({
type: DataType.CHAR(200),
allowNull: false,
})
public Username: string;
@Column({
type: DataType.CHAR(50),
allowNull: false,
validate: {
isEmail: true,
isUnique: async (value: string, next: Function): Promise<any> => {
const exists = await Users.findOne({ where: { Email: value } });
if (exists) {
const error = new Error('This email is already used.');
next(error);
}
next();
}
}
})
public Email: string;
@Column({
type: DataType.CHAR(250),
allowNull: false,
})
public Password: string;
@Column({
type: DataType.CHAR(250),
allowNull: true,
})
public Salt: string;
@CreatedAt
public createdAt: Date;
@UpdatedAt
public updatedAt: Date;
}
Great, now we can go to the next point!
6. User provider and interface
Next, what we will do now is to create a user provider. Inside the user folder create users.provider.ts
file, and inside that file, let's create the following code.
import { Users } from './users.entity';
export const UsersProviders = {
provide: 'USERS_REPOSITORY',
useValue: Users
}
When it's done, let's open the module and add the provider there.
@Module({
providers: [UsersProviders],
exports: [
UsersProviders,
]
})
Now let's create the interface, where we will define the types of the User object.
In the user
folder, create a new folder interface
and in that folder, create user.interface.ts
file. In this file, create the following interface.
export interface IUser {
id: number;
Username: string;
Email: string;
Password: string;
Salt: string;
Accounts: [];
}
Cool, now we can go to the most exciting part of this lesson, ready?
7. User service and controller
At this point, we will create a user.service.ts
file, and inside this file, we will build the function which will be saving data to the database.
Open the newly created file and type the following code.
import { Injectable, Inject } from '@nestjs/common';
import { Users } from './users.entity';
import * as jwt from 'jsonwebtoken';
import { jwtConfig } from './../../config/jwtConfig';
import crypto = require('crypto');
@Injectable()
export class UsersService {
constructor(
@Inject('USERS_REPOSITORY') private usersRepository: typeof Users,
) { }
public async create(user: any): Promise<object> {
const exists = await Users.findOne({ where: { Email: user.Email } });
if (exists) {
throw new Error('This email is already used.');
} else {
user.Salt = crypto.randomBytes(128).toString('base64');
user.Password = crypto.createHmac('sha256', user.Password + user.Salt).digest('hex');
const newUser: any = await this.usersRepository.create<Users>(user);
const jwtToken = jwt.sign(user, process.env.JWT_KEY, jwtConfig);
newUser.Token = jwtToken;
return newUser;
}
}
}
Great, it looks like that's it! We just need a controller right now, where we will set the endpoint and API method.
Let's create a user.controller.ts
file and create the following code.
import { Controller, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { UsersService } from './users.service';
import { IUser } from './interfaces/user.interface';
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) { }
@Post('register')
public async register(@Body() user: IUser): Promise<any> {
const result: any = await this.usersService.create(user,);
if (!result.success) {
throw new HttpException(result.message, HttpStatus.BAD_REQUEST);
}
return result;
}
}
Now, we need to inject those file to the module file.
@Module({
controllers: [UsersController],
providers: [UsersProviders, UsersService],
exports: [
UsersService,
UsersProviders,
]
})
It seems like the first part of our registration is ready, so let's create the second part, where we will create an account for each registered user.
8. Accounts module and entity
Let's use the Nest CLI and create a new module.
$ nest generate module modules/accounts
You should see the new module created in a new folder, and as previously, we have nothing to do in the accounts module file right now. So, let's create the accounts.entity.ts
file and make sure your file looks like the code below.
import { Table, Column, Model, DataType, CreatedAt, UpdatedAt, ForeignKey, BelongsTo } from 'sequelize-typescript';
import { TableOptions } from 'sequelize-typescript';
import { Users } from '../user/users.entity';
const tableOptions: TableOptions = { timestamp: true, tableName: 'Accounts' } as TableOptions;
@Table(tableOptions)
export class Accounts extends Model<Accounts> {
@Column({
type: DataType.INTEGER,
allowNull: false,
autoIncrement: true,
unique: true,
primaryKey: true,
})
public id: number;
@Column({
type: DataType.CHAR(200),
allowNull: false,
})
public Type: string;
@Column({
type: DataType.CHAR(200),
allowNull: false,
})
public Name: string;
@Column({
type: DataType.INTEGER,
allowNull: true,
})
public Balance: number;
@ForeignKey(() => Users)
public UserId: number;
@BelongsTo(() => Users, {
as: 'Users',
foreignKey: 'UserId',
targetKey: 'id',
})
public Users: Users;
@CreatedAt
public createdAt: Date;
@UpdatedAt
public updatedAt: Date;
}
Great, it's ready, so we can go to the next steps.
9. Accounts provider and interface
Let's create a provider for the accounts module right now. In the accounts
folder, please create the accounts.provider.ts
file. In this file, you need to set the provider with the following code.
import { Accounts } from './accounts.entity';
export const AccountsProviders = {
provide: 'ACCOUNTS_REPOSITORY',
useValue: Accounts
};
As previously we need an interface, so let's create the new folder called interfaces
and inside that file create the accounts.interface.ts
file with the following object inside.
export interface IAccount {
id: number;
Type: string;
Name: string;
Balance: number;
UserId: number;
}
We are ready to create AccountsService and controller.
10. Accounts service and controller
In the accounts
folder, let's create an accounts.service.ts
file, and in this file, you need to create the following function.
import { Injectable, Inject } from '@nestjs/common';
import { Accounts } from './accounts.entity';
@Injectable()
export class AccountsService {
constructor(
@Inject('ACCOUNTS_REPOSITORY')
private accountsRepository: typeof Accounts
) { }
public async create(UserId: number): Promise<object> {
const account = {
Name: 'Account',
Type: 'Personal Account',
Balance: 100,
UserId: UserId,
}
const newAccount: any = await this.accountsRepository.create<Accounts>(account);
return newAccount;
}
}
As you can see, we are setting the hardcoded values for our initial user account as it will be the default one, and later user will be able to make changes.
Let's create an accounts.controller.ts
file in the same folder. And in that file type the following code, so we will be able to use it from the endpoint as well.
import { AccountsService } from './accounts.service';
import { Controller, Post, Body, HttpException, HttpStatus } from '@nestjs/common';
import { IAccount } from './interfaces/accounts.interface';
@Controller('accounts')
export class AccountsController {
constructor(private accountsService: AccountsService) { }
@Post('create-account')
public async register(@Body() UserId: number): Promise<any> {
const result: any = await this.accountsService.create(UserId);
if (!result.success) {
throw new HttpException(result.message, HttpStatus.BAD_REQUEST);
}
return result;
}
}
We are almost at the end of the lesson. We just need to update a few files and test it.
11. Add AccountsService to UserService
We will be using the function to create an account in our register function. But first, we need to update modules, so let's open accounts.module.ts
file and make sure it looks like the code below.
@Module({
imports: [DatabaseModule],
controllers: [AccountsController],
providers: [AccountsProviders, AccountsService],
exports: [AccountsProviders, AccountsService]
})
When it's saved, open the other module file users.module.ts
and update it as well.
@Module({
controllers: [UsersController],
imports: [AccountsModule],
providers: [UsersProviders, UsersService],
exports: [
UsersService,
UsersProviders,
]
})
So, we can import it in the user.service.ts
. The file should look like the code below now.
import { Injectable, Inject } from '@nestjs/common';
import { Users } from './users.entity';
import * as jwt from 'jsonwebtoken';
import { jwtConfig } from './../../config/jwtConfig';
import { AccountsService } from './../accounts/accounts.service';
import crypto = require('crypto');
@Injectable()
export class UsersService {
constructor(
@Inject('USERS_REPOSITORY') private usersRepository: typeof Users,
private accountsService: AccountsService,
) { }
public async create(user: any): Promise<object> {
const exists = await Users.findOne({ where: { Email: user.Email } });
if (exists) {
throw new Error('This email is already used.');
} else {
user.Salt = crypto.randomBytes(128).toString('base64');
user.Password = crypto.createHmac('sha256', user.Password + user.S
alt).digest('hex');
const newUser: any = await this.usersRepository.create<Users>(user);
const jwtToken = jwt.sign(user, process.env.JWT_KEY, jwtConfig);
newUser.Token = jwtToken;
if (newUser) {
this.accountsService.create(newUser.id)
}
return newUser;
}
}
}
Ok, we've passed the create function from accountsService and created a new account each time the new user registers.
To make the whole backend working, we need to update the user entity and database provider. Let's open the user.entity.ts file, and we will add a few lines of code at the end of the User class.
@HasMany(() => Accounts, 'UserId')
public Accounts: Accounts[];
Now, let's open database.provider.ts file and import both entities. When the import is done, inject them as models.
import { Sequelize } from 'sequelize-typescript';
import { Users } from '../user/users.entity';
import { Accounts } from '../accounts/accounts.entity';
export const databaseProvider = [
{
provide: 'SEQUELIZE',
useFactory: async () => {
const sequelize = new Sequelize({
dialect: 'postgres',
host: process.env.DB_HOST,
port: 5432,
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
});
sequelize.addModels([Users, Accounts]);
return sequelize;
}
}
]
And voila! Let's test it!
12. Testing
I'll be using the Postman to test our API right now. If you are not running the app yet, please do it using nest start
or npm run start
, and when it's ready to open the Postman. In the image below, you can see my setting, so you can try similar. Also, you can open your database to see if the data is there.
I hope it works for you as well!
Conclusion
In this lesson, we build the registration of a new user and creating the default account.
In the next lessons, we are going to work on the login feature.
If you didn't manage to get all the code correctly, jump into our Github and find your bugs.
Node.js Course - Lesson 2: User registration - Code
Also, remember to jump into other courses where we are building GoLang backend for the same app, AI for investment, and frontend with Angular 9.
Thank you for reading,
Anna from Duomly
Top comments (4)
Add a note about putting
.env
in your.gitignore
file so it doesn't go into source control.Thanks for finding that and sorry!
Dotenv shouldn’t go to repo, I had to miss it.
Going to fix :)
Wow, I was just looking for an up to date tutorial about this. Thanks for sharing!
Thanks, I’m happy you like our content :)