mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1225 words
6 minutes
Auction Platform Built with Nest.js and Angular—with Jest Testing and CI/CD
2024-12-07
2024-12-08

Bidding Platform Based on Nest.js and Angular#

Overall Project Description#

This project is a bidding platform based on Nest.js and Angular, designed to provide a complete bidding and management system. Its main features include user registration and login, project creation and management, bid management, and user role management. The frontend is built with Angular, the backend with Nest.js, and PostgreSQL is used as the database. API documentation is provided through Swagger. The project is deployed on a DigitalOcean Droplet, with the frontend served through Nginx.

前端 (Angular)
↓(API 请求)
Cognito (用户认证)
↓(验证通过后请求转发)
后端 (Nest.js)
↓(数据库查询)
数据库 (PostgreSQL)
↑(数据返回)
后端 (Nest.js)
↑(处理后的响应)
前端 (Angular)

Project Structure#

  • frontend: Contains all frontend code and is built with Angular.
  • backend: Contains all backend code and is built with Nest.js.
  • .github: Contains GitHub Actions configuration files for continuous integration and deployment.

Backend#

Backend Development#

The backend is built with Nest.js, providing a modular and scalable architecture. Its main features include user authentication, project management, and bid management. The backend uses TypeORM for database operations and supports multiple database types.

Backend Technology Stack#

  • Nest.js: Used to build efficient, scalable Node.js server-side applications.
  • TypeORM: An ORM framework used for database interaction.
  • Swagger: Used to generate API documentation, making it easier for developers to inspect and test APIs.

Backend Build Steps#

  1. Install dependencies: Run npm install in the backend directory to install all required dependencies.
  2. Configure environment variables: Create a .env file in the project root directory and configure database connection details and other environment variables.
  3. Run the development server: Use npm run start:dev to start the development server with hot reload support.
  4. Production build: Use npm run build to create a production build. The generated files are located in the dist directory.

Database#

The project uses PostgreSQL as its database, with all database operations handled through TypeORM. The database initialization script is located at backend/SQL/init-script.sql and can be used to create and initialize the database. The backend has a clear code structure, and its modular design makes feature expansion and maintenance more convenient.

Backend Security and Authentication#

Backend security and authentication are implemented through AWS Cognito in combination with Nest.js interceptors and services, ensuring proper user authentication and authorization.

Security and Authentication Architecture#

  • AWS Cognito: Used for user registration, login, and authentication. Cognito provides secure user pool and identity pool management.
  • Nest.js Interceptor: Used to intercept HTTP requests and validate the JWT Token in the request headers, ensuring the user’s identity is legitimate.
  • Service Layer: Responsible for interacting with Cognito and associating Cognito users with user information stored in the database.

Implementation Steps#

  1. Configure a Cognito user pool: Create a user pool in AWS Cognito and configure an app client to support JWT Token generation and validation.
  2. JWT interceptor: Create an interceptor in Nest.js that parses the JWT Token from the request headers, validates it, and attaches the user information to the request object.

import { Injectable, NestInterceptor, ExecutionContext, CallHandler, UnauthorizedException } from ‘@nestjs/common’; import { Observable } from ‘rxjs’; import { AuthService } from ’./auth.service’;

@Injectable() export class JwtInterceptor implements NestInterceptor { constructor(private readonly authService: AuthService) {}

intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization?.split(’ ’)[1];

if (!token) {
throw new UnauthorizedException('Token not found');
}
const user = this.authService.validateToken(token);
if (!user) {
throw new UnauthorizedException('Invalid token');
}
request.user = user;
return next.handle();

} }

```

3. User service: Create a user service responsible for retrieving user information from the database and associating it with Cognito users. Store user information in the database using the Cognito ID as the unique identifier. ```typescript import { Injectable } from ‘@nestjs/common’; import { UsersRepository } from ’./users.repository’;

@Injectable() export class UsersService { constructor(private readonly usersRepository: UsersRepository) {}

async findOrCreateUser(cognitoId: string, email: string) { let user = await this.usersRepository.findOneByCognitoId(cognitoId); if (!user) { user = await this.usersRepository.create({ cognitoId, email }); } return user; } }

```

4. Role and permission management: Define user roles in the database, such as administrator, client, and bidder, and validate permissions by role in the interceptor. ```typescript import { Injectable, CanActivate, ExecutionContext } from ‘@nestjs/common’; import { Reflector } from ‘@nestjs/core’;

@Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get<string[]>(‘roles’, context.getHandler()); if (!roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; return roles.includes(user.role); } }

```
Add the `@Roles('admin')` decorator to APIs that require permission validation to specify the required role.
```typescript

@Post() @Roles(‘admin’) createProject(@Body() createProjectDto: CreateProjectDto) { return this.projectsService.createProject(createProjectDto); }

```

This approach enables the backend to manage user identities and permissions effectively, ensuring system security and reliability.

Project Management Implementation#

The project management module demonstrates how the Controller calls the Service, which then interacts with the database.

Controller#

In ProjectsController, routes and methods are defined to handle HTTP requests.

import { Controller, Get, Post, Body, Param, Put, Delete } from '@nestjs/common';
import { ProjectsService } from './projects.service';
import { ProjectsDto } from '../entities/DTO/projects.dto';
@Controller('projects')
export class ProjectsController {
constructor(private readonly projectsService: ProjectsService) {}
@Get()
findAll() {
return this.projectsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: number) {
return this.projectsService.findOne(id);
}
@Post()
create(@Body() projectDto: ProjectsDto) {
return this.projectsService.create(projectDto);
}
@Put(':id')
update(@Param('id') id: number, @Body() projectDto: ProjectsDto) {
return this.projectsService.update(id, projectDto);
}
@Delete(':id')
delete(@Param('id') id: number) {
return this.projectsService.delete(id);
}
}

Service#

ProjectsService is responsible for handling business logic and interacting with the database.

import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm';
import { Project } from '../entities/projects.entity';
import { ProjectsDto } from '../entities/DTO/projects.dto';
@Injectable()
export class ProjectsService {
constructor(private dataSource: DataSource) {}
findAll() {
return this.dataSource.getRepository(Project).find();
}
findOne(id: number) {
return this.dataSource.getRepository(Project).findOneBy({ project_id: id });
}
create(project: ProjectsDto) {
return this.dataSource.getRepository(Project).save(project);
}
update(id: number, project: ProjectsDto) {
return this.dataSource.getRepository(Project).update(id, project);
}
delete(id: number) {
return this.dataSource.getRepository(Project).delete(id);
}
}

Database Entity#

The Project entity defines the structure of a project in the database. The entity is defined using the @Entity() decorator, while columns are defined using the @Column() decorator.

import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Project {
@PrimaryGeneratedColumn()
project_id: number;
@Column()
title: string;
@Column()
description: string;
@Column('decimal')
budget_min: number;
@Column('decimal')
budget_max: number;
@Column('date')
deadline: Date;
@Column({ default: 'open' })
status: string;
}

With this approach, the Controller handles HTTP requests, the Service handles business logic, and the database entity defines the data structure. Together, they provide complete project management functionality.

Frontend#

The frontend is built with Angular, providing a user-friendly interface and interactive experience. Its main features include project display, bid management, user registration, and login.

Frontend Technology Stack#

  • Angular: Used to build modern single-page applications.
  • RxJS: Used to process asynchronous data streams.
  • Angular CLI: Provides powerful development tools and a command-line interface.

Frontend Build Steps#

  1. Install dependencies: Run npm install in the frontend directory to install all required dependencies.
  2. Development server: Use ng serve to start the development server, which runs at http://localhost:4200/ by default.
  3. Production build: Use ng build to create a production build. The generated files are located in the dist directory.

Project Details Component#

The frontend application consists of multiple components, with each component responsible for a specific functional module. The following is an example component implementation. ProjectDetailComponent is used to display detailed information about a single project.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProjectsService } from '../../services/projects.service';
import { BidsService } from '../../services/bids.service';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-project-detail',
templateUrl: './project-detail.component.html',
styleUrls: ['./project-detail.component.css']
})
export class ProjectDetailComponent implements OnInit {
project: any = null;
bids: any[] = [];
loading = false;
error = '';
userRole: string = '';
constructor(
private route: ActivatedRoute,
private projectsService: ProjectsService,
private bidsService: BidsService,
private authService: AuthService
) {}
ngOnInit() {
this.userRole = this.authService.getUserRole();
const projectId = this.route.snapshot.paramMap.get('id');
if (projectId) {
this.loadProject(+projectId);
this.loadBids(+projectId);
}
}
loadProject(id: number) {
this.loading = true;
this.projectsService.getProjectById(id).subscribe({
next: (data) => {
this.project = data;
this.loading = false;
},
error: (err) => {
this.error = '加载项目详情失败';
this.loading = false;
console.error('加载项目详情错误:', err);
}
});
}
loadBids(projectId: number) {
this.bidsService.getBidsByProjectId(projectId).subscribe({
next: (data) => {
this.bids = data;
},
error: (err) => {
console.error('加载投标列表错误:', err);
}
});
}
}

Template File#

project-detail.component.html defines the display structure for the project details.

<div class="project-detail">
<div *ngIf="loading" class="loading">
加载中...
</div>
<div *ngIf="error" class="error">
{{ error }}
</div>
<div *ngIf="project && !loading" class="project-info">
<h2>{{ project.title }}</h2>
<div class="project-meta">
<p>预算: ¥{{ project.budget_min }} - ¥{{ project.budget_max }}</p>
<p>截止日期: {{ project.deadline | date }}</p>
<p>状态: {{ project.status }}</p>
</div>
<div class="project-description">
<h3>项目描述</h3>
<p>{{ project.description }}</p>
</div>
<app-bid-form
*ngIf="userRole === 'bidder' && project.status === 'open'"
[projectId]="project.project_id"
(bidSubmitted)="loadBids(project.project_id)">
</app-bid-form>
<div class="bids-section" *ngIf="userRole === 'client' || userRole === 'admin'">
<h3>投标列表</h3>
<div *ngFor="let bid of bids" class="bid-card">
<p>投标人: {{ bid.bidder_id }}</p>
<p>投标金额: ¥{{ bid.amount }}</p>
<p>投标说明: {{ bid.message }}</p>
<p>状态: {{ bid.status }}</p>
</div>
</div>
</div>
</div>

This approach enables the frontend application to provide rich user interactions and data presentation capabilities.

Testing#

The project uses Jest for unit and integration testing to ensure code correctness and stability. ESLint is also used for code quality checks to maintain a consistent coding style.

Jest Testing#

Jest is a powerful JavaScript testing framework that supports assertions, mocking, and snapshot testing.

Jest Configuration#

Configure Jest in the project’s package.json:

"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage"
},
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "src",
"testRegex": ".*\\\\.spec\\\\.ts$",
"transform": {
"^.+\\\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}

Example Test#

The following is a simple service test example:

import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsService } from './projects.service';
describe('ProjectsService', () => {
let service: ProjectsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ProjectsService],
}).compile();
service = module.get<ProjectsService>(ProjectsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('findOne', () => {
it('应该返回单个项目', async () => {
const result = await service.findOne(1);
expect(result).toEqual(mockProject);
});
});
});

ESLint Code Quality Checks#

ESLint is a tool for identifying and reporting patterns in JavaScript code, helping developers maintain code consistency and quality.

ESLint Configuration#

Create a .eslintrc.js file in the project root directory:

module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

Running ESLint#

Add a script to package.json:

"scripts": {
"lint": "eslint . --ext .ts"
}

Run npm run lint to check code quality. By using Jest and ESLint, the project can ensure code correctness and consistency while improving development efficiency and code quality.

CI/CD#

The project uses GitHub Actions to implement continuous integration and continuous deployment (CI/CD), ensuring that the code is automatically built, tested, and deployed after every commit.

GitHub Actions#

GitHub Actions is a tool for automating software development workflows. By defining workflow files, build, test, and deployment tasks can be executed automatically within the repository.

Workflow Configuration#

The CI/CD workflow is defined in the project’s .github/workflows/deploy.yml file:

name: CI/CD Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20.18.0'
- name: Install dependencies
run: |
cd backend
npm install
cd ../frontend
npm install
- name: Run tests
run: |
cd backend
npm run test:cov
cd ../frontend
npm run test
- name: Lint code
run: |
cd backend
npm run lint
cd ../frontend
npm run lint
- name: Build project
run: |
cd backend
npm run build
cd ../frontend
npm run build
- name: Create Release Package
run: |
mkdir -p build
cd backend
tar -czvf ../build/backend.tar.gz dist
cd ../frontend
tar -czvf ../build/frontend.tar.gz dist
cd ..
- name: Deploy to DigitalOcean
uses: digitalocean/action-doctl@v2
with:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
run: |
# 部署脚本或命令

Deployment#

  • DigitalOcean: The project is deployed on a DigitalOcean Droplet, with the frontend served through Nginx.
  • Automated workflow: Whenever code is committed to the main branch, GitHub Actions automatically runs the build, test, and deployment workflow. This approach enables the project to respond quickly to code changes, ensuring that every commit undergoes rigorous testing and validation before being deployed automatically to the production environment.
Share

If this article helped you, please share it with others!

Auction Platform Built with Nest.js and Angular—with Jest Testing and CI/CD
https://dreaife.tokyo/en/posts/bidding-platform-nest-angular/
Author
dreaife
Published at
2024-12-07
License
CC BY-NC-SA 4.0

Some information may be outdated

Related Posts Smart
1
Angular-Based Animation Showcase Website with Login and Registration (Cognito)
PROJECT This Angular-based web application lets users browse and search for anime on Bangumi, with Cognito for user authentication. It supports automated builds and deployment to GitHub Pages via GitHub Actions. The main technology stack includes Angular 16, TypeScript, HTML, and CSS. The project has a clear structure and provides user login, registration, search, and anime calendar features.
2
A Summary of My Thoughts on the Market on X
market Drawing on complex systems and game theory, this post unpacks the underlying logic of market dynamics, emotional reflexivity, and the consensus underpinning monetary value, helping readers look beyond short-term volatility and develop a deeper understanding of market expectations and inflation transmission.
3
About an EOA Wallet Signature Verification and Related Content
WEB3 A developer-focused guide to EOA wallet signature verification, covering secp256k1, ECDSA r/s/v signatures, public key recovery, keccak-256 address derivation, and how SIWE proves wallet ownership without exposing the private key.
4
Basic On-Chain Operations for EOA Wallets
WEB3 Quickly master the core on-chain operations of Web3 wallets. Covers the principles behind creating EOA and HD wallets, mnemonic generation and the BIP derivation process, an in-depth look at SIWE and EIP-712 on-chain verification, and the full lifecycle of transaction construction, signing, and broadcasting to help you understand the underlying logic of wallets.
5
An EVM wallet login interface for EOAs
WEB3 A practical EVM/EOA wallet login walkthrough covering connect wallet, SIWE-style messages, wagmi signing, nonce handling, and backend verification, explaining why login separates address connection from signature-based ownership proof.

Table of Contents