Giai đoạn 3: Phát triển Backend với TypeScript

Chào mừng đến với Giai đoạn 3
Bạn đã hoàn thành kiến thức cơ bản về TypeScript và xây dựng ứng dụng React type-safe. Giờ là lúc xây dựng backend cho các ứng dụng đó.
Trong giai đoạn này, bạn sẽ học cách tạo REST và GraphQL API production-ready với Node.js 22+, Express.js và TypeScript. Kết nối database với Prisma, triển khai authentication, xử lý lỗi, và deploy lên production—tất cả với type safety hoàn toàn.
Thời gian: 3 tuần, 1-2 giờ/ngày
Yêu cầu: Giai đoạn 1 và Giai đoạn 2
Những gì bạn sẽ học
✅ Thiết lập dự án Node.js + TypeScript với tooling phù hợp
✅ Xây dựng REST API với Express.js và type-safe routing
✅ Tích hợp PostgreSQL/MySQL với Prisma ORM
✅ Triển khai JWT authentication và authorization
✅ Xử lý validation với Zod schemas
✅ Cấu trúc dự án theo layers (Controller/Service/Repository)
✅ Xây dựng GraphQL API với Apollo Server
✅ Viết tests với Jest và Supertest
✅ Deploy lên production với Docker và PM2
✅ Monitoring API với logging và health checks
Thiết lập dự án Node.js + TypeScript
1. Khởi tạo dự án
mkdir typescript-api
cd typescript-api
npm init -y2. Cài đặt TypeScript và dependencies
# TypeScript core
npm install --save-dev typescript @types/node tsx nodemon
# Development tools
npm install --save-dev @types/express @types/cors @types/dotenv
npm install --save-dev eslint prettier eslint-config-prettier
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
# Runtime dependencies
npm install express cors dotenv3. Cấu hình TypeScript (tsconfig.json)
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}4. Thêm scripts vào package.json
{
"scripts": {
"dev": "nodemon --watch src --exec tsx src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"lint": "eslint src/**/*.ts",
"test": "jest"
},
"type": "module"
}5. Cấu trúc dự án
src/
├── index.ts # Entry point
├── config/ # Configuration
│ └── database.ts
├── routes/ # API routes
│ ├── index.ts
│ ├── auth.routes.ts
│ └── user.routes.ts
├── controllers/ # Request handlers
│ ├── auth.controller.ts
│ └── user.controller.ts
├── services/ # Business logic
│ ├── auth.service.ts
│ └── user.service.ts
├── repositories/ # Database access
│ └── user.repository.ts
├── middleware/ # Express middleware
│ ├── auth.middleware.ts
│ ├── error.middleware.ts
│ └── validation.middleware.ts
├── types/ # Type definitions
│ └── models.ts
└── utils/ # Utilities
├── logger.ts
└── jwt.tsXây dựng REST API với Express.js
Entry Point (src/index.ts)
import express, { Application, Request, Response } from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import routes from './routes/index.js';
import { errorHandler } from './middleware/error.middleware.js';
dotenv.config();
const app: Application = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Health check
app.get('/health', (_req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API routes
app.use('/api', routes);
// Error handling
app.use(errorHandler);
app.listen(PORT, () => {
console.log(`🚀 Server running on http://localhost:${PORT}`);
});Validation với Zod
Định nghĩa schemas
// src/schemas/user.schema.ts
import { z } from 'zod';
export const createUserSchema = z.object({
body: z.object({
name: z.string().min(2, 'Tên phải có ít nhất 2 ký tự'),
email: z.string().email('Email không hợp lệ'),
password: z
.string()
.min(8, 'Mật khẩu phải có ít nhất 8 ký tự')
.regex(/[A-Z]/, 'Mật khẩu phải chứa ít nhất 1 chữ hoa')
.regex(/[0-9]/, 'Mật khẩu phải chứa ít nhất 1 số'),
}),
});
// Infer types từ schemas
export type CreateUserInput = z.infer<typeof createUserSchema>['body'];Validation middleware
// src/middleware/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AnyZodObject, ZodError } from 'zod';
export const validateRequest =
(schema: AnyZodObject) => async (req: Request, res: Response, next: NextFunction) => {
try {
await schema.parseAsync({
body: req.body,
query: req.query,
params: req.params,
});
next();
} catch (error) {
if (error instanceof ZodError) {
return res.status(400).json({
success: false,
error: 'Validation failed',
details: error.errors,
});
}
next(error);
}
};Tích hợp Database với Prisma
1. Cài đặt Prisma
npm install --save-dev prisma
npm install @prisma/client
npx prisma init2. Định nghĩa schema (prisma/schema.prisma)
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String
password String
role Role @default(USER)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
@@map("users")
}
model Post {
id Int @id @default(autoincrement())
title String
content String
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("posts")
}
enum Role {
USER
ADMIN
}3. Chạy migration
# Tạo migration
npx prisma migrate dev --name init
# Generate TypeScript types
npx prisma generate4. Sử dụng Prisma Client
import { prisma } from '../config/database.js';
// Type-safe queries với autocomplete
const users = await prisma.user.findMany({
where: { isActive: true },
include: { posts: true },
orderBy: { createdAt: 'desc' },
});
// Create với relations
const user = await prisma.user.create({
data: {
email: '[email protected]',
name: 'John Doe',
password: 'hashedPassword',
posts: {
create: [{ title: 'First Post', content: 'Hello!' }],
},
},
include: { posts: true },
});Authentication với JWT
1. JWT utility functions
// src/utils/jwt.ts
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
export interface JwtPayload {
userId: number;
email: string;
role: string;
}
export const generateToken = (payload: JwtPayload): string => {
return jwt.sign(payload, JWT_SECRET, { expiresIn: '7d' });
};
export const verifyToken = (token: string): JwtPayload => {
return jwt.verify(token, JWT_SECRET) as JwtPayload;
};2. Authentication middleware
// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { verifyToken, JwtPayload } from '../utils/jwt.js';
declare global {
namespace Express {
interface Request {
user?: JwtPayload;
}
}
}
export const authenticate = (req: Request, res: Response, next: NextFunction) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
success: false,
error: 'No token provided',
});
}
const token = authHeader.substring(7);
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
success: false,
error: 'Invalid or expired token',
});
}
};3. Protected routes
import { authenticate, authorize } from '../middleware/auth.middleware.js';
// Chỉ authenticated users
router.delete('/users/:id', authenticate, userController.deleteUser);
// Chỉ admins
router.get('/admin/users', authenticate, authorize('ADMIN'), adminController.getAllUsers);Error Handling
Custom error classes
// src/utils/errors.ts
export class AppError extends Error {
constructor(
public statusCode: number,
public message: string,
public isOperational = true
) {
super(message);
Object.setPrototypeOf(this, AppError.prototype);
}
}
export class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(404, message);
}
}
export class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(400, message);
}
}Global error handler
// src/middleware/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors.js';
export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
if (err instanceof AppError) {
return res.status(err.statusCode).json({
success: false,
error: err.message,
});
}
console.error('Unexpected error:', err);
return res.status(500).json({
success: false,
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message,
});
};Testing với Jest và Supertest
1. Cài đặt dependencies
npm install --save-dev jest @types/jest ts-jest supertest @types/supertest2. Integration test example
// src/__tests__/auth.test.ts
import request from 'supertest';
import app from '../index.js';
describe('Authentication', () => {
describe('POST /api/auth/register', () => {
it('nên đăng ký user mới thành công', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'John Doe',
email: '[email protected]',
password: 'Password123',
});
expect(res.status).toBe(201);
expect(res.body.success).toBe(true);
expect(res.body.data.user).toHaveProperty('id');
expect(res.body.data).toHaveProperty('token');
});
it('nên trả về 400 nếu email không hợp lệ', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'Jane Doe',
email: 'invalid-email',
password: 'Password123',
});
expect(res.status).toBe(400);
expect(res.body.success).toBe(false);
});
});
});3. Chạy tests
# Run all tests
npm test
# Run với coverage
npm test -- --coverageGraphQL API với Apollo Server
1. Định nghĩa schema
// src/graphql/schema.ts
export const typeDefs = `#graphql
type User {
id: ID!
email: String!
name: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type AuthPayload {
user: User!
token: String!
}
type Query {
me: User
users: [User!]!
posts: [Post!]!
}
type Mutation {
register(email: String!, password: String!, name: String!): AuthPayload!
login(email: String!, password: String!): AuthPayload!
createPost(title: String!, content: String!): Post!
}
`;2. Implement resolvers
// src/graphql/resolvers.ts
import { prisma } from '../config/database.js';
import { AuthService } from '../services/auth.service.js';
export const resolvers = {
Query: {
me: async (_parent: any, _args: any, context: Context) => {
if (!context.user) {
throw new GraphQLError('Not authenticated');
}
return prisma.user.findUnique({
where: { id: context.user.userId },
});
},
users: async () => {
return prisma.user.findMany();
},
},
Mutation: {
register: async (_parent: any, args: any) => {
const authService = new AuthService();
return authService.register(args.email, args.password, args.name);
},
createPost: async (_parent: any, args: any, context: Context) => {
if (!context.user) {
throw new GraphQLError('Not authenticated');
}
return prisma.post.create({
data: {
title: args.title,
content: args.content,
authorId: context.user.userId,
},
});
},
},
};Deployment
1. Production build
npm run build2. Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
COPY prisma ./prisma
EXPOSE 3000
CMD ["node", "dist/index.js"]3. Docker Compose
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@postgres:5432/db
depends_on:
- postgres
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=db
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:Kế hoạch học tập 3 tuần
Tuần 1: Setup & REST API
- Thiết lập dự án TypeScript
- Xây dựng REST API với Express
- Tích hợp Prisma ORM
- Validation với Zod
Tuần 2: Authentication & GraphQL
- JWT authentication
- Role-based authorization
- GraphQL API với Apollo Server
- Advanced validation
Tuần 3: Testing & Deployment
- Unit tests với Jest
- Integration tests với Supertest
- Docker containerization
- Deploy lên production
Best Practices
✅ Sử dụng layered architecture (Controller → Service → Repository)
✅ Enable strict mode trong tsconfig.json
✅ Tránh any type - dùng unknown cho truly unknown types
✅ Validate inputs với Zod
✅ Hash passwords với bcrypt
✅ Centralize error handling trong middleware
✅ Use environment variables cho secrets
✅ Paginate large results
✅ Index database cho frequently queried columns
Common Pitfalls (Lỗi thường gặp)
❌ Không xử lý errors trong async controllers
❌ Trả về passwords cho client
❌ Hardcode secrets và configuration
❌ Bỏ qua TypeScript errors với @ts-ignore
❌ Không validate user input
❌ Thiếu connection pooling cho database
Bước tiếp theo
Sau Giai đoạn 3
Giai đoạn 4: Full-Stack Integration (sắp ra mắt)
- Kết nối frontend và backend với tRPC
- End-to-end type safety
- Real-time features với WebSockets
- Deployment strategies
Advanced Topics:
- Microservices với TypeScript
- Event-driven architecture
- Message queues (Redis, RabbitMQ)
- Background jobs (BullMQ)
Tổng kết
Trong Giai đoạn 3, bạn đã học cách xây dựng backend production-ready với TypeScript:
✅ Project setup: Cấu hình TypeScript, ESLint và tooling hiện đại
✅ REST APIs: Xây dựng Express.js API type-safe với layered architecture
✅ Database: Tích hợp Prisma ORM với PostgreSQL
✅ Validation: Sử dụng Zod cho runtime type checking
✅ Authentication: Triển khai JWT-based auth với bcrypt
✅ GraphQL: Tạo Apollo Server với resolvers
✅ Testing: Viết unit và integration tests với Jest
✅ Deployment: Containerize với Docker và deploy lên production
Hành trình full-stack:
- ✅ Giai đoạn 1: TypeScript Fundamentals
- ✅ Giai đoạn 2: Frontend Development
- ✅ Giai đoạn 3: Backend Development (bài này)
- ⏭️ Giai đoạn 4: Full-Stack Integration (sắp ra mắt)
Bắt đầu xây dựng: Cách tốt nhất để học là thực hành. Chọn một ý tưởng dự án (blog, todo app, e-commerce API) và triển khai sử dụng các patterns bạn đã học!
Sẵn sàng kết nối frontend và backend? Hãy xem Giai đoạn 4: Full-Stack Integration để có end-to-end type safety với tRPC.
Deep Dive
Muốn đi sâu hơn vào các chủ đề trong giai đoạn này?
📘 Deep Dive: Kiểu TypeScript nâng cao → — Conditional type, mapped type, branded type và nhiều hơn 📘 Deep Dive: Phát triển API Node.js → — Pattern API production-ready 📘 Deep Dive: Database & ORM → — Prisma, Drizzle và best practices
Trước đó: Giai đoạn 2: Frontend Development Tiếp theo: Giai đoạn 4: Full-Stack Integration
Happy coding! 🚀
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.