Back to blog

Bắt Đầu Với Express.js

expressnodejsbackendjavascriptrest-api
Bắt Đầu Với Express.js

Chào mừng bạn đến với Lộ Trình Học Express.js! Series này sẽ hướng dẫn bạn từ những kiến thức cơ bản của Express.js đến việc xây dựng các REST APIs và ứng dụng web production-ready với Node.js.

Trong bài đầu tiên này, bạn sẽ học các khái niệm nền tảng của Express.js, thiết lập môi trường phát triển, tạo server đầu tiên, và xây dựng một REST API đơn giản. Khi kết thúc, bạn sẽ hiểu cách Express hoạt động và sẵn sàng cho các chủ đề nâng cao hơn.

Bạn Sẽ Học Được Gì

Kết Quả Học Tập:
✅ Hiểu Express.js là gì và tại sao nó là Node.js framework phổ biến nhất
✅ Thiết lập môi trường phát triển Node.js từ đầu
✅ Tạo server Express đầu tiên và hiểu cách nó hoạt động
✅ Xử lý HTTP requests (GET, POST, PUT, DELETE)
✅ Hiểu khái niệm middleware và request-response cycle
✅ Xây dựng REST API đơn giản với CRUD operations

Express.js Là Gì?

Express.js (hay đơn giản là "Express") là một web application framework tối giản, linh hoạt và nhanh cho Node.js. Đây là Node.js framework phổ biến nhất với hơn 60 triệu lượt tải về mỗi tuần trên npm.

Tại Sao Chọn Express?

Express được ưa chuộng vì:

  1. Tối giản và Không Áp đặt - Cho bạn tự do cấu trúc ứng dụng theo ý muốn
  2. Nhanh và Nhẹ - Overhead tối thiểu, hiệu suất xuất sắc
  3. Hệ sinh thái Trưởng thành - Hàng nghìn middleware packages có sẵn
  4. Dễ học - API đơn giản, trực quan, thân thiện với người mới
  5. Sẵn sàng cho Production - Được sử dụng bởi các công ty như Uber, IBM và Accenture

Express vs Các Node.js Frameworks Khác

Tính năngExpressFastifyNestJSKoa
Triết lýTối giảnHiệu suấtCó quan điểmTối giản
Độ khó họcDễDễTrung bìnhDễ
Hiệu suấtTốtXuất sắcTốtTốt
TypeScriptTùy chọnTích hợp sẵnTích hợp sẵnTùy chọn
Use CaseĐa mục đíchHiệu suất caoỨng dụng doanh nghiệpDevs có kinh nghiệm

Khi nào nên dùng Express:

  • Xây dựng REST APIs hoặc web applications
  • Cần tính linh hoạt trong kiến trúc
  • Muốn hệ sinh thái middleware lớn
  • Ưu tiên sự đơn giản và dễ học
  • Làm việc với team nhỏ đến trung bình

Khi nào KHÔNG nên dùng Express:

  • Cần hiệu suất tối đa (xem xét Fastify)
  • Muốn TypeScript và kiến trúc nghiêm ngặt (xem xét NestJS)
  • Xây dựng ứng dụng real-time chủ yếu (xem xét Socket.IO native)

Thiết Lập Môi Trường Phát Triển

Yêu Cầu

Trước khi bắt đầu, đảm bảo bạn có:

  • Node.js 18+ đã cài đặt (kiểm tra với node --version)
  • npm hoặc yarn package manager
  • Code editor (khuyên dùng VS Code)
  • Kiến thức JavaScript cơ bản (ES6+)

Cài Đặt Node.js

Nếu bạn chưa có Node.js, tải về từ nodejs.org hoặc dùng version manager:

Sử dụng nvm (khuyên dùng):

# Cài đặt nvm (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
 
# Cài đặt Node.js LTS mới nhất
nvm install --lts
nvm use --lts
 
# Xác minh cài đặt
node --version  # Nên hiển thị v20.x.x hoặc cao hơn
npm --version   # Nên hiển thị v10.x.x hoặc cao hơn

Sử dụng Homebrew (macOS):

brew install node

Tạo Project Đầu Tiên

Hãy tạo một Express project mới từ đầu.

Bước 1: Khởi tạo project

# Tạo thư mục project
mkdir express-todo-api
cd express-todo-api
 
# Khởi tạo npm project
npm init -y

Lệnh này tạo file package.json với các thiết lập mặc định.

Bước 2: Cài đặt Express

npm install express

Bước 3: Cài đặt development dependencies

npm install --save-dev nodemon

nodemon tự động khởi động lại server khi files thay đổi - rất cần thiết cho development.

Bước 4: Cập nhật package.json scripts

Sửa package.json và thêm các scripts sau:

{
  "name": "express-todo-api",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "dev": "nodemon index.js"
  },
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

Cấu Trúc Project

Đây là cấu trúc khuyên dùng cho project đơn giản của chúng ta:

express-todo-api/
├── index.js          # File ứng dụng chính
├── package.json      # Dependencies và scripts
├── package-lock.json # Phiên bản dependencies đã lock
└── node_modules/     # Các packages đã cài đặt

Với các project lớn hơn, bạn sẽ tổ chức code vào các folders như routes/, controllers/, middleware/, v.v. Chúng ta sẽ đề cập trong các bài sau.

Server Express Đầu Tiên

Hãy tạo một server "Hello World" tối giản để hiểu các khái niệm cơ bản.

Tạo index.js:

// Import Express
const express = require('express');
 
// Tạo Express application
const app = express();
 
// Định nghĩa port
const PORT = 3000;
 
// Tạo một route
app.get('/', (req, res) => {
  res.send('Hello World!');
});
 
// Khởi động server
app.listen(PORT, () => {
  console.log(`Server đang chạy tại http://localhost:${PORT}`);
});

Chạy server:

npm run dev

Truy cập http://localhost:3000 trong trình duyệt - bạn sẽ thấy "Hello World!".

Hiểu Code

Hãy phân tích từng phần làm gì:

const express = require('express');

Import Express module.

const app = express();

Tạo một Express application instance. Object app này có các methods để định tuyến HTTP requests, cấu hình middleware, render HTML, v.v.

app.get('/', (req, res) => {
  res.send('Hello World!');
});

Định nghĩa một route handler cho GET requests đến root path (/). Callback function nhận:

  • req (request) - Chứa dữ liệu về incoming request
  • res (response) - Methods để gửi responses trả về client
app.listen(PORT, () => { ... });

Bind Express app vào port 3000 và bắt đầu lắng nghe connections.

Hiểu Routes và HTTP Methods

Routes xác định cách ứng dụng phản hồi với client requests tại các endpoints cụ thể (URLs).

Cú Pháp Route

app.METHOD(PATH, HANDLER)
  • METHOD - HTTP method (get, post, put, delete, etc.)
  • PATH - URL path
  • HANDLER - Function thực thi khi route được match

HTTP Methods và CRUD

Express hỗ trợ tất cả HTTP methods, nhưng 4 methods này map với CRUD operations:

HTTP MethodCRUD OperationMục đích
GETReadLấy dữ liệu
POSTCreateTạo dữ liệu mới
PUTUpdateCập nhật dữ liệu có sẵn
DELETEDeleteXóa dữ liệu

Ví Dụ Route Cơ Bản

// GET request - lấy dữ liệu
app.get('/users', (req, res) => {
  res.send('Lấy tất cả users');
});
 
// POST request - tạo dữ liệu
app.post('/users', (req, res) => {
  res.send('Tạo một user');
});
 
// PUT request - cập nhật dữ liệu
app.put('/users/:id', (req, res) => {
  res.send(`Cập nhật user ${req.params.id}`);
});
 
// DELETE request - xóa dữ liệu
app.delete('/users/:id', (req, res) => {
  res.send(`Xóa user ${req.params.id}`);
});

Route Parameters

Sử dụng : để định nghĩa route parameters:

app.get('/users/:userId', (req, res) => {
  const userId = req.params.userId;
  res.send(`User ID: ${userId}`);
});
 
// Truy cập với: GET /users/123
// req.params.userId sẽ là "123"

Nhiều parameters:

app.get('/users/:userId/posts/:postId', (req, res) => {
  const { userId, postId } = req.params;
  res.json({ userId, postId });
});
 
// Truy cập với: GET /users/5/posts/42
// req.params = { userId: "5", postId: "42" }

Query Strings

Query strings truyền dữ liệu bổ sung trong URL:

app.get('/search', (req, res) => {
  const { q, page, limit } = req.query;
  res.json({ query: q, page, limit });
});
 
// Truy cập với: GET /search?q=express&page=1&limit=10
// req.query = { q: "express", page: "1", limit: "10" }

Giới Thiệu Middleware

Middleware functions là khái niệm cốt lõi của Express. Chúng là các functions có quyền truy cập vào request object (req), response object (res), và middleware function tiếp theo trong request-response cycle.

Request-Response Cycle

Mỗi middleware có thể:

  1. Thực thi bất kỳ code nào
  2. Sửa đổi request và response objects
  3. Kết thúc request-response cycle
  4. Gọi middleware tiếp theo trong stack

Middleware Signature

function middleware(req, res, next) {
  // Làm gì đó
  next(); // Chuyển control sang middleware tiếp theo
}

Function next chuyển control sang middleware tiếp theo. Nếu bạn không gọi next(), request sẽ bị treo.

Built-in Middleware

Express có built-in middleware cho các tác vụ phổ biến:

1. express.json() - Parse JSON request bodies

// Thêm trước routes
app.use(express.json());
 
app.post('/users', (req, res) => {
  // Bây giờ req.body chứa parsed JSON
  const { name, email } = req.body;
  res.json({ message: 'User đã tạo', user: { name, email } });
});

2. express.urlencoded() - Parse URL-encoded bodies

app.use(express.urlencoded({ extended: true }));

3. express.static() - Serve static files

// Serve files từ thư mục "public"
app.use(express.static('public'));
 
// Bây giờ files trong public/ có thể truy cập:
// public/style.css -> http://localhost:3000/style.css

Application-level Middleware

Bạn có thể tạo custom middleware để chạy trên tất cả requests:

// Logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next();
});
 
// Bây giờ tất cả requests sẽ được log

Thứ Tự Thực Thi Quan Trọng

Middleware thực thi theo thứ tự bạn định nghĩa:

const express = require('express');
const app = express();
 
// 1. Chạy đầu tiên
app.use((req, res, next) => {
  console.log('First');
  next();
});
 
// 2. Chạy thứ hai (parsing JSON)
app.use(express.json());
 
// 3. Chạy thứ ba
app.use((req, res, next) => {
  console.log('Third');
  next();
});
 
// 4. Route handler chạy cuối
app.get('/', (req, res) => {
  res.send('Hello');
});

Quan trọng: Đặt middleware nên chạy trên TẤT CẢ requests trước các routes.

Hands-on Project: Todo REST API

Hãy xây dựng một REST API đơn giản để quản lý todos. Chúng ta sẽ dùng in-memory array làm "database" (sẽ đề cập database thật trong các bài sau).

Code Hoàn Chỉnh

Thay thế index.js với code này:

const express = require('express');
const app = express();
const PORT = 3000;
 
// Middleware
app.use(express.json());
 
// Logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next();
});
 
// In-memory database
let todos = [
  { id: 1, title: 'Học Express.js', completed: false },
  { id: 2, title: 'Xây dựng REST API', completed: false },
];
 
// GET /api/todos - Lấy tất cả todos
app.get('/api/todos', (req, res) => {
  res.json({
    success: true,
    data: todos,
  });
});
 
// GET /api/todos/:id - Lấy một todo
app.get('/api/todos/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const todo = todos.find(t => t.id === id);
 
  if (!todo) {
    return res.status(404).json({
      success: false,
      message: 'Không tìm thấy todo',
    });
  }
 
  res.json({
    success: true,
    data: todo,
  });
});
 
// POST /api/todos - Tạo todo mới
app.post('/api/todos', (req, res) => {
  const { title } = req.body;
 
  if (!title) {
    return res.status(400).json({
      success: false,
      message: 'Title là bắt buộc',
    });
  }
 
  const newTodo = {
    id: todos.length + 1,
    title,
    completed: false,
  };
 
  todos.push(newTodo);
 
  res.status(201).json({
    success: true,
    data: newTodo,
  });
});
 
// PUT /api/todos/:id - Cập nhật todo
app.put('/api/todos/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { title, completed } = req.body;
 
  const todoIndex = todos.findIndex(t => t.id === id);
 
  if (todoIndex === -1) {
    return res.status(404).json({
      success: false,
      message: 'Không tìm thấy todo',
    });
  }
 
  // Cập nhật todo
  if (title !== undefined) todos[todoIndex].title = title;
  if (completed !== undefined) todos[todoIndex].completed = completed;
 
  res.json({
    success: true,
    data: todos[todoIndex],
  });
});
 
// DELETE /api/todos/:id - Xóa todo
app.delete('/api/todos/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const todoIndex = todos.findIndex(t => t.id === id);
 
  if (todoIndex === -1) {
    return res.status(404).json({
      success: false,
      message: 'Không tìm thấy todo',
    });
  }
 
  const deletedTodo = todos.splice(todoIndex, 1)[0];
 
  res.json({
    success: true,
    data: deletedTodo,
  });
});
 
// 404 handler cho unknown routes
app.use((req, res) => {
  res.status(404).json({
    success: false,
    message: 'Không tìm thấy route',
  });
});
 
// Khởi động server
app.listen(PORT, () => {
  console.log(`Server đang chạy tại http://localhost:${PORT}`);
});

Kiểm Thử API

Khởi động server:

npm run dev

Test với curl hoặc REST client:

1. Lấy tất cả todos:

curl http://localhost:3000/api/todos

2. Lấy một todo:

curl http://localhost:3000/api/todos/1

3. Tạo todo mới:

curl -X POST http://localhost:3000/api/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"Học middleware"}'

4. Cập nhật todo:

curl -X PUT http://localhost:3000/api/todos/1 \
  -H "Content-Type: application/json" \
  -d '{"completed":true}'

5. Xóa todo:

curl -X DELETE http://localhost:3000/api/todos/1

Sử Dụng REST Client (Khuyên dùng)

Thay vì curl, dùng REST client để test dễ hơn:

  • Postman - Full-featured GUI client
  • Insomnia - Giao diện đơn giản, sạch sẽ
  • VS Code REST Client - Test từ .http files trong VS Code
  • Thunder Client - VS Code extension, nhẹ

Ví dụ file .http cho VS Code REST Client:

### Lấy tất cả todos
GET http://localhost:3000/api/todos
 
### Lấy một todo
GET http://localhost:3000/api/todos/1
 
### Tạo todo mới
POST http://localhost:3000/api/todos
Content-Type: application/json
 
{
  "title": "Học middleware"
}
 
### Cập nhật todo
PUT http://localhost:3000/api/todos/1
Content-Type: application/json
 
{
  "completed": true
}
 
### Xóa todo
DELETE http://localhost:3000/api/todos/1

Hiểu Code

1. Cấu trúc Response:

Chúng ta dùng format response nhất quán:

{
  success: true,   // hoặc false cho errors
  data: { ... },   // dữ liệu thực tế
  message: "..."   // error message tùy chọn
}

2. HTTP Status Codes:

  • 200 - OK (mặc định cho GET, PUT, DELETE thành công)
  • 201 - Created (cho POST thành công)
  • 400 - Bad Request (lỗi client, như thiếu required field)
  • 404 - Not Found (resource không tồn tại)
  • 500 - Internal Server Error (sẽ xử lý sau)

3. Validation:

if (!title) {
  return res.status(400).json({
    success: false,
    message: 'Title là bắt buộc',
  });
}

Luôn validate user input. Từ khóa return quan trọng - nó ngăn code tiếp tục thực thi.

4. 404 Handler:

app.use((req, res) => {
  res.status(404).json({
    success: false,
    message: 'Không tìm thấy route',
  });
});

Middleware này chạy nếu không route nào match. Đặt ở CUỐI file.

Tham Chiếu Response Methods

Express cung cấp nhiều methods để gửi responses:

// Gửi JSON
res.json({ message: 'Hello' });
 
// Gửi plain text
res.send('Hello World');
 
// Gửi status code
res.sendStatus(404);  // Gửi "Not Found"
 
// Set status và gửi
res.status(201).json({ message: 'Created' });
 
// Redirect
res.redirect('/new-url');
 
// Download file
res.download('/path/to/file.pdf');
 
// Gửi file
res.sendFile('/path/to/file.html');
 
// Set headers
res.set('Content-Type', 'application/json');
res.send('{"message":"Hello"}');

Các Lỗi Thường Gặp Cần Tránh

1. Không gọi next() trong middleware

// ❌ Sai - request sẽ bị treo
app.use((req, res, next) => {
  console.log('Hello');
  // Quên gọi next()
});
 
// ✅ Đúng
app.use((req, res, next) => {
  console.log('Hello');
  next();
});

2. Gửi nhiều responses

// ❌ Sai - lỗi: "Cannot set headers after they are sent"
app.get('/test', (req, res) => {
  res.send('First');
  res.send('Second');  // Lỗi!
});
 
// ✅ Đúng - dùng return để ngăn thực thi tiếp
app.get('/test', (req, res) => {
  if (someCondition) {
    return res.send('First');
  }
  res.send('Second');
});

3. Thứ tự middleware

// ❌ Sai - routes định nghĩa trước middleware
app.get('/api/todos', (req, res) => {
  // req.body sẽ là undefined
  const { title } = req.body;
});
 
app.use(express.json());  // Quá muộn!
 
// ✅ Đúng - middleware trước routes
app.use(express.json());
 
app.get('/api/todos', (req, res) => {
  const { title } = req.body;  // Hoạt động!
});

4. Không xử lý errors

// ❌ Sai - không xử lý error
app.get('/users/:id', (req, res) => {
  const user = database.findUser(req.params.id);  // Có thể throw error
  res.json(user);
});
 
// ✅ Đúng - xử lý potential errors
app.get('/users/:id', (req, res) => {
  try {
    const user = database.findUser(req.params.id);
    if (!user) {
      return res.status(404).json({ error: 'Không tìm thấy user' });
    }
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }
});

Tóm Tắt và Những Điểm Chính

Những Gì Đã Đề Cập:
✅ Express.js là một Node.js framework tối giản, linh hoạt để xây dựng web applications và APIs
✅ Thiết lập Node.js project với npm init, cài Express, và dùng nodemon cho auto-reload
✅ Tạo routes với app.get(), app.post(), app.put(), app.delete()
✅ Hiểu middleware - functions xử lý requests trước khi đến route handlers
✅ Built-in middleware: express.json() cho parsing JSON, express.static() cho static files
✅ Xây dựng CRUD REST API hoàn chỉnh để quản lý todos với status codes và validation đúng cách

Các Khái Niệm Chính:

  • Routes map URLs và HTTP methods tới handler functions
  • Middleware functions thực thi theo thứ tự và có thể sửa req/res hoặc gọi next()
  • Request object (req) chứa dữ liệu về incoming request (params, query, body)
  • Response object (res) có methods để gửi responses (json, send, status, etc.)
  • Status codes truyền đạt kết quả request (200, 201, 400, 404, 500)

Best Practices:

  • Luôn gọi next() trong middleware trừ khi gửi response
  • Dùng return khi gửi responses để tránh multiple responses
  • Đặt middleware trước routes
  • Validate user input
  • Dùng response format nhất quán
  • Dùng HTTP status codes phù hợp

Bài Tập Thực Hành

Thử các bài tập này để củng cố kiến thức:

  1. Mở rộng Todo API:

    • Thêm filtering: GET /api/todos?completed=true
    • Thêm pagination: GET /api/todos?page=1&limit=10
    • Thêm search: GET /api/todos?search=learn
  2. Tạo API Mới:

    • Xây dựng notes API đơn giản với categories
    • Mỗi note nên có: id, title, content, category, created_at
  3. Thêm Custom Middleware:

    • Tạo middleware log request duration
    • Tạo middleware thêm timestamp vào tất cả responses
  4. Error Handling:

    • Tạo centralized error handling middleware
    • Xử lý malformed JSON requests một cách graceful
  5. Response Enhancements:

    • Thêm pagination metadata (total, page, pages)
    • Thêm timestamps vào tất cả responses

Tiếp Theo Là Gì?

Trong bài tiếp theo, chúng ta sẽ đi sâu vào Express Middleware. Bạn sẽ học:

  • Middleware hoạt động như thế nào bên trong
  • Tạo custom middleware cho authentication và logging
  • Popular third-party middleware (morgan, cors, helmet)
  • Error handling middleware patterns
  • Async error handling

Nền tảng này sẽ giúp bạn xây dựng các ứng dụng robust, production-ready hơn.

Tài Nguyên Bổ Sung

Official Documentation:

Tài Nguyên Học:

Tools:

Các Bước Tiếp Theo Trong Series:

  • Bài 2: Middleware Deep Dive
  • Bài 3: Routing and Request Handling
  • Bài 4: Working with MongoDB and Mongoose
  • Bài 5: Working with PostgreSQL and Prisma

Tiếp tục xây dựng, và hẹn gặp bạn trong bài tiếp theo! 🚀

📬 Đă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.