Introduction:
In our journey to create a scalable microservices architecture, we are now ready to add an authentication microservice to our application. This microservice will handle all authentication and identity access management-related functionalities, allowing us to authenticate users across different parts of our application seamlessly.
Creating the Authentication Microservice:
Let's start by creating the authentication microservice. We will use the NestJS CLI to generate the necessary files and directories.
Step 1: Generate the Authentication Microservice
In your terminal, run the following command to generate the authentication microservice:
nest generate app auth
This command creates a new directory named "auth" and updates the project configuration files accordingly. You can find the newly created "auth" application in the nest-cli.json.
Step 2: Implementing User Functionality
The core of our authentication microservice is handling user functionality, such as user creation and authentication. We want the microservice to be able to create users and authenticate them by providing a JSON Web Token (JWT). Let's start by implementing user creation.
Defining the User DTO:
We need a DTO (Data Transfer Object) to handle the data required for user creation. Create a folder named "DTO" inside the "users" directory to do this run the following command on your terminal:
mkdir apps/auth/src/users/dto
then:
touch apps/auth/src/users/dto/create-user.dto.ts
define a "create-user.dto.ts" file with the following content:
import { IsEmail, IsString, IsStrongPassword } from 'class-validator';
export class CreateUserDTO {
@IsEmail()
email: string;
@IsString()
@IsStrongPassword()
password: string;
}
Creating the User Module, Controller and Service:
In the "auth" application, generate a user module using the following commands:
nest generate module users --project auth
nest generate controller users --project auth
nest generate service users --project auth
Defining the User Schema and Repository:
Create a new directory named "models" inside the "users" directory. In this directory, define the user schema in a "user.schema.ts" file:
to do this run the following command on your terminal:
mkdir apps/auth/src/users/models
then:
touch apps/auth/src/users/models/user.schema.ts
then populate the user.schema.ts with the following code:
import { AbstractDocument } from '@app/common';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
@Schema({ versionKey: false })
export class UserDocument extends AbstractDocument {
@Prop({ type: String, required: true })
email: string;
@Prop({ type: String, required: true })
password: string;
}
export const UserSchema = SchemaFactory.createForClass(UserDocument);
we need to define user.repository.ts that extends our Abstract repository just like we did on our reservations app
to do this run the following command on your terminal:
touch apps/auth/src/users/user.repository.ts
then populate the it with the following code:
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Injectable, Logger } from '@nestjs/common';
import { AbstractRepository } from '@app/common';
import { UserDocument } from './models/user.schema';
@Injectable()
export class UserRepository extends AbstractRepository<UserDocument> {
protected readonly logger = new Logger(UserRepository.name);
constructor(
@InjectModel(UserDocument.name)
protected readonly userModel: Model<UserDocument>,
) {
super(userModel);
}
}
Defining the Users Service:
In your "users.service.ts" file, add the following code to create user:
import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { CreateUserDTO } from '../dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(private readonly userRepository: UserRepository) {}
async create(createUserDto: CreateUserDTO) {
return this.userRepository.create({
...createUserDto,
});
}
}
Defining the Users Controller:
In your "users.controller.ts" file, add the following code to create user it connects to user.service create method that we just created:
import { Body, Controller, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDTO } from '../dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly userService: UsersService) {}
@Post()
async createUser(@Body() createUserDto: CreateUserDTO) {
return this.userService.create(createUserDto);
}
}
Update User Module to connect to our Database
Open your "users.module.ts" file, and update the content with the following code:
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { DatabaseModule, LoggerModule } from '@app/common';
import { UserDocument, UserSchema } from './models/user.schema';
import { UserRepository } from './user.repository';
@Module({
imports: [
DatabaseModule,
DatabaseModule.forFeature([
{ name: UserDocument.name, schema: UserSchema },
]),
LoggerModule,
],
controllers: [UsersController],
providers: [UsersService, UserRepository],
})
export class UsersModule {}
what we did was we just imported the DatabaseModule and the LoggerModule then add UserRepository in our provider array so that we can use it in our user module
Update User Module to connect to our Database
Open your "auth.module.ts" file, and update the content with the following code:
import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { UsersModule } from './users/users.module';
import { LoggerModule } from '@app/common';
@Module({
imports: [UsersModule, LoggerModule],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
Test our auth setup
to test it run the following command on your terminal:
npm run start:dev auth
if you see this logs
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [NestFactory] Starting Nest application...
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] DatabaseModule dependencies initialized +57ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] MongooseModule dependencies initialized +2ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] LoggerModule dependencies initialized +1ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] ConfigHostModule dependencies initialized +1ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] AuthModule dependencies initialized +0ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] LoggerModule dependencies initialized +0ms
[Nest] 21036 - 10/27/2023, 4:31:48 PM LOG [InstanceLoader] ConfigModule dependencies initialized +3ms
[Nest] 21036 - 10/27/2023, 4:32:18 PM ERROR [MongooseModule] Unable to connect to the database. Retrying (1)...
then our setup is successful !
Note: we have a mongoose error because we are not yet connected to our database we will do that in the next section by dockerizing out auth module
Integrating the Authentication Microservice with Docker:
Now that we have implemented the core functionalities of our authentication microservice, it's time to Dockerize it. This will allow us to run the microservice alongside other microservices seamlessly.
Dockerizing the Authentication Microservice:
To Dockerize the authentication microservice, we need to create a Dockerfile. In the "auth" directory, create a new "Dockerfile"
to do this run the following command on your terminal:
touch apps/auth/Dockerfile
and populate with the following content:
---
# Use Node Alpine as the base image for development
FROM node:alpine as development
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm install -g && npm install
COPY . .
RUN npm run build
# Create a production image
FROM node:alpine as production
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm install --only=production
COPY --from=development /usr/src/app/dist ./dist
COPY --from=development /usr/src/app/package.json ./package.json
CMD [ "node", "dist/apps/auth/main" ]
Update Docker Compose:
In your project's root directory, our existing "docker-compose.yaml" file let's define our auth service.
update your existing code with the following content:
services:
reservations:
build:
context: .
dockerfile: apps/reservations/Dockerfile
target: development
command: npm run start:dev reservations
ports:
- '3000:3000'
volumes:
- .:/usr/src/app
auth:
build:
context: .
dockerfile: apps/auth/Dockerfile
target: development
command: npm run start:dev auth
ports:
- '3001:3001'
mongo:
image: mongo
This configuration specifies the build context and Dockerfile for the authentication microservice, sets the target to "development," and specifies the command to run the microservice. It also maps Port 3001 in the container to Port 3001 on the host.
Update auth main.ts file
Since we have declared in our Docker service that our auth service should run on port 3001, we also need to declare it in our apps/auth/main.ts open it then populate it with the following code:
import { NestFactory } from '@nestjs/core';
import { AuthModule } from './auth.module';
import { ValidationPipe } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
async function bootstrap() {
const app = await NestFactory.create(AuthModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
app.useLogger(app.get(Logger));
await app.listen(3001);
}
bootstrap();
Testing the Authentication Microservice with Docker:
With both the User and Authentication microservices Dockerized, you can now run them using Docker Compose. In your project's root directory, run the following command:
docker-compose up
Docker Compose will start both microservices and a MongoDB container, allowing them to communicate with each other.
Testing User Creation:
Now, you can test the user creation functionality of the authentication microservice. Use a tool like Postman or your preferred API client to send a POST request to the following URL:
http://localhost:3001/users
Ensure you provide a valid JSON payload with an email and a strong password in the request body. For example:
{
"email": "jayvee@xample.com",
"password": "Jayvee2345!"
}
If you receive a 201 Created response, it means the user creation was successful. If there are validation errors, the authentication microservice will provide feedback on what went wrong.
Conclusion:
In this tutorial, we've created an authentication microservice using NestJS and Docker. We've implemented user creation functionality, Dockerized the microservice, and ensured that it can communicate with other microservices using Docker Compose.
By following this guide, you have set the foundation for a scalable and secure authentication system that can be integrated into various parts of your microservices architecture. With this authentication microservice, you can now proceed to implement user authentication and authorization features across your application.
Top comments (2)
If we are creating microservices then why we are not using '@nestjs/microservices' ?
There is no LoggerModule exported in libs/common!