Trang chủ Kiến Thức Công Nghệ Lập trình REST API TODO List với Typescript Express
Công Nghệ

Lập trình REST API TODO List với Typescript Express

Chia sẻ
Lập trình REST API TODO List với Typescript Express
Chia sẻ

Trong bài viết này, mình sẽ hướng dẫn các bạn xây dựng REST API với Typescript, Express và MySQL. Nếu bạn chưa biết về các syntax cơ bản, các bạn có thể đọc tại đây.

Khoá học lập trình microservices với Typescript và Express

1. Phân tích và thiết lập User Story cơ bản

Bài viết này mình sẽ tập trung vào phần backend, nên mình sẽ để hình phần giao diện đơn giản về TODO List phía dưới các bạn có thể tham khảo qua nhé.

Giao diện todo list

Mình khuyên các bạn nên xây dựng User Story chi tiết, vì nó sẽ giúp bạn tránh được việc sẽ bị sót các tính năng, kiểm soát các tính năng cần cho dự án.

  1. Service không đăng nhập phân biệt người dùng với nhau.
  2. Người dùng có thể tạo mới một TODO Item.
  3. Người dùng có thể xem được toàn bộ các TODO Item (thường sẽ có phân trang), hiển thị tổng TODO Item đã hoàn thành / tổng TODO Item.
  4. Người dùng có thể edit mark done bất kỳ một TODO Item.
  5. Người dùng có thể xoá một TODO Item.

Các bạn theo mảng backend sẽ cảm thấy hơi lạ với việc xây dựng User Story như thế này. Vì các backend developer trong các công ty không cần phải làm việc này. Mình nêu đến để các bạn hiểu rõ quy trình từ phân tích đến triển khai.

2. Thiết kế cơ sở dữ liệu từ User Story

Dựa trên User Story phía trên mình vừa xây dựng, mình có 3 danh từ được in đậm: “Service“, “Người dùng“, “TODO Item“. Và vì trong ví dụ này, không yêu cầu đăng nhập nên “Service” và “Người dùng” có thể không cần quan tâm đến.

Với danh từ “TODO Item” mình sẽ cần chứa title và status. Dựa vào đó ta sẽ xây dựng cơ sở dữ liệu như sau:

  • Id (Primary Key, Auto Increment): định danh (Identifier) cho từng TODO Item, vì là PK nên sẽ không trùng lặp, không thể NULL.
  • Title: tiêu đề cho TODO Item. Cột này chắc chắn sẽ chứa nội dung. Cụ thể trong MySQL thì nó là varchar.
  • Status: trạng thái của TODO Item. Vì chỉ có 2 giá trị các bạn có thể dùng 01. Tuy nhiên mình vẫn thích dùng kiểu Enum để rõ ràng và dễ mở rộng về sau hơn.
  • Created At: Thời gian Item được tạo trên hệ thống. Cột này chỉ là một tuỳ chọn thêm. Theo mình mỗi table nên có cột này để tiện quản lý về sau.
  • Updated At: Thời gian Item được update lần cuối trên hệ thống. Cột này cũng dùng để quản lý thêm thôi.

Phần code tạo bảng trong MySQL như sau:

Sql

CREATE TABLE `todo_items` (
  `id` int NOT NULL AUTO_INCREMENT,
  `title` varchar(150) CHARACTER SET utf8 NOT NULL,
  `status` enum('Doing','Finished') DEFAULT 'Doing',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Sau khi chạy thì database của mình sẽ như sau:

Database Todo list

3. Thiết kế các REST API cho TODO List service

Mình thấy đây là bước cực kỳ quan trọng, nhưng mình thấy nhiều bạn thường bỏ qua. Nếu bạn chưa hiểu rõ về REST API nên được thiết kế thế nào thì xem lại bài này:

REST API là gì? Cách thiết kế RESTful API bạn chưa biết

REST API là gì? Làm thế nào để thiết kế RESTful API hiệu quả? Cập nhật những thông tin mới nhất về REST API nhé!

200Lab BlogViệt Trần

Mình sẽ thiết kế CRUD (Create-Read-Update-Delete) API như vầy:

  1. POST /v1/items tạo mới TODO Item với dữ liệu chỉ cần có title là đủ. Thuộc tính status nên để mặc định là “Doing“. API này sẽ trả về ID của TODO Item sau khi tạo thành công. Ràng buộc đơn giản là “title không rỗng hoặc chỉ chứa toàn khoảng trắng” là ok.
  2. GET /v1/items lấy danh sách các TODO Items. Nếu có phân trang thì có thể dùng thêm query string ?page=1&limit=10. Một trang sẽ hiển thị tối đa 10 items. Mặc định page1limit10.
  3. PUT /v1/items/:id update tiêu đề hoặc trạng thái của một Item thông qua ID của nó. Vì API này chúng ta có thể truyền lên cả 2 thông tin hoặc chỉ một trong 2 nên các bạn có thể dùng method PATCH sẽ chuẩn chỉ hơn. Vì PUT thông dụng hơn cho các API update nên mình chọn trong ví dụ này.
  4. DELETE /v1/items/:id xoá một TODO Item thông qua ID của nó. Trong ví dụ này mình sẽ xoá luôn trong table. Trong thực tế, hầu hết tất cả trường hợp là không nên xoá mà chỉ chuyển đổi trạng thái deleted thôi.
  5. GET /v1/items/:id lấy toàn bộ thông tin chi tiết của một TODO Item thông qua ID của nó. Theo giao diện demo thì chúng ta không cần API này, tuy nhiên 200Lab để vào cho đủ bộ CRUD nha.

4. Xây dựng REST API với Typescript Express

Mình đã hoàn tất phần chuẩn bị ở các mục phía trên, tiến hành code thôi nào.

Bash

npm init -y
npm install express typescript ts-node mysql2 @types/node @types/express --save-dev
npx tsc --init

Đây là tsconfig.json

JSON

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

Nếu bạn chưa có MySQL thì bạn có thể dùng Docker để chạy container MySQL với câu lệnh sau:

Bash

docker run -d --name demo-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=my-root-pass -e MYSQL_DATABASE=todo_db mysql:8.3.1

Để dễ dàng hơn mình sẽ để code trong cùng một file để các bạn dễ theo dõi
src/index.ts

Typescript

import express, { Request, Response } from 'express';
import mysql, { RowDataPacket, ResultSetHeader } from 'mysql2';

interface TodoItem extends RowDataPacket {
  id: number;
  title: string;
  status: 'Doing' | 'Finished';
  created_at: string;
  updated_at: string;
}

interface CreateTodoItem {
  title: string;
}

interface UpdateTodoItem {
  title?: string;
  status?: 'Doing' | 'Finished';
}

const db = mysql.createConnection({
  host: 'localhost',
  user: process.env.USER,
  password: process.env.PASS,
  database: process.env.DATABASE,
});

db.connect((err) => {
  if (err) {
    console.error('Error connect:', err.message);
  } else {
    console.log('Connected to MySQL');
  }
});

const app = express();
app.use(express.json());

app.post('/v1/items', (req: Request<{}, {}, CreateTodoItem>, res: Response) => {
  const { title } = req.body;

  if (!title || !title.trim()) {
    return res.status(400).json({ message: 'Title cannot be empty' });
  }

  const sql = 'INSERT INTO todo_items (title) VALUES (?)';
  db.query<ResultSetHeader>(sql, [title], (err, result) => {
    if (err) {
      return res.status(500).json({ message: 'Database error', error: err });
    }
    return res.status(201).json({ id: result.insertId });
  });
});

app.get('/v1/items', (req: Request, res: Response) => {
  const page = parseInt(req.query.page as string) || 1;
  const limit = parseInt(req.query.limit as string) || 10;
  const offset = (page - 1) * limit;

  const sql = 'SELECT * FROM todo_items LIMIT ? OFFSET ?';
  db.query<TodoItem[]>(sql, [limit, offset], (err, results) => {
    if (err) {
      return res.status(500).json({ message: 'Database error', error: err });
    }
    return res.status(200).json(results);
  });
});

app.put(
  '/v1/items/:id',
  (req: Request<{ id: string }, {}, UpdateTodoItem>, res: Response) => {
    const { id } = req.params;
    const { title, status } = req.body;

    if (!title && !status) {
      return res.status(400).json({ message: 'Nothing to update' });
    }

    const updates: any[] = [];
    let sql = 'UPDATE todo_items SET ';

    if (title) {
      sql += 'title = ? ';
      updates.push(title);
    }

    if (status) {
      if (updates.length > 0) {
        sql += ', ';
      }
      sql += 'status = ? ';
      updates.push(status);
    }

    sql += 'WHERE id = ?';
    updates.push(id);

    db.query<ResultSetHeader>(sql, updates, (err, result) => {
      if (err) {
        return res.status(500).json({ message: 'Database error', error: err });
      }

      if (result.affectedRows === 0) {
        return res.status(404).json({ message: 'Item not found' });
      }

      return res.status(200).json({ message: 'Item updated successfully' });
    });
  }
);

app.delete('/v1/items/:id', (req: Request<{ id: string }>, res: Response) => {
  const { id } = req.params;

  const sql = 'DELETE FROM todo_items WHERE id = ?';
  db.query<ResultSetHeader>(sql, [id], (err, result) => {
    if (err) {
      return res.status(500).json({ message: 'Database error', error: err });
    }

    if (result.affectedRows === 0) {
      // Không tìm thấy item với ID đã cung cấp
      return res.status(404).json({ message: 'Item not found' });
    }

    return res.status(200).json({ message: 'Item deleted successfully' });
  });
});

app.get('/v1/items/:id', (req: Request<{ id: string }>, res: Response) => {
  const { id } = req.params;

  const sql = 'SELECT * FROM todo_items WHERE id = ?';
  db.query<TodoItem[]>(sql, [id], (err, result) => {
    if (err) {
      return res.status(500).json({ message: 'Database error', error: err });
    }
    if (result.length === 0) {
      return res.status(404).json({ message: 'Item not found' });
    }
    return res.status(200).json(result[0]);
  });
});

const PORT = 8080;
app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});

Cuối cùng, mình sẽ dùng POSTMAN để test API nhé!

  • POST /v1/items
Tạo mới todo item
  • GET /v1/items
Get todo items list
  • PUT /v1/items/:id
update todo item
  • DELETE /v1/items/:id
Delete todo item
  • GET /v1/items/:id
Get todo items

5. Kết luận

Qua bài viết này, bạn sẽ hiểu rõ và có thể tự mình hoàn tất được một REST API TODO List đơn giản với Typescript, vì đây là một ví dụ nên việc để code chỉ trong file index.ts không phải là best practice trong thực tế.

Nếu các bạn chưa tự tin, cảm thấy khó khăn khi học các kiến thức nâng cao với Typescript thì có thể tham khảo khóa học Typescript tại 200Lab nhé.

Khoá Học Lập Trình Microservices Với Typescript Và Express

Khoá học lập trình microservices với typescript và express giúp bạn có một nền tảng vững chắc + có project để build portfolio trở thành dev backend.

200LabTien Nguyen

Một số bài có thể bạn sẽ quan tâm:

  • Cách debugger trên VSCode cho Typescript
  • Hiểu về Module Alias trong Typescript. Tại sao phải dùng Alias?
  • Khám Phá Cách Hoạt Động Của TypeScript Compiler
Chia sẻ

Để lại bình luận

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Bài viết cùng chuyên mục
Công Nghệ

13 projects giúp bạn trở thành master với Web3 và Blockchain – Từ cơ bản đến nâng cao

Việc học code ban đầu sẽ không dễ dàng và khó hiểu,...

Data Entry là gì? Mọi thứ cần biết về công việc nhập liệu
Công Nghệ

Data Entry là gì? Mọi thứ cần biết về công việc nhập liệu

Nhập liệu là loại công việc văn thư bao gồm việc sử...

Sự khác nhau giữa nghiên cứu định tính và định lượng
Công Nghệ

Sự khác nhau giữa nghiên cứu định tính và định lượng

Khi thực hiện dự án nghiên cứu, chúng tôi thường tự hỏi...

Unit Testing là gì? Cách thực hiện Unit Testing
Công Nghệ

Unit Testing là gì? Cách thực hiện Unit Testing

Nếu công việc hiện tại của bạn gắn liền với các dự...

Công Nghệ

Lập trình web là gì? Các bước lập trình 1 trang web.

Lập trình web là một công việc mà nhiều người lựa chọn...

Hướng dẫn Data Analysis trong Excel
Công Nghệ

Hướng dẫn Data Analysis trong Excel

Phân tích dữ liệu với Excel là hướng dẫn cung cấp cái...

Top 5 công cụ Business Intelligence (BI)
Công Nghệ

Top 5 công cụ Business Intelligence (BI)

Các công cụ BI giúp tổ chức phân tích những khối dữ...

Công Nghệ

Sự khác biệt giữa Blockchain vs Cryptocurrency (Tiền điện tử)

Blockchain và tiền điện tử là hai thuật ngữ thường được sử...

So sánh StatelessWidget và StatefulWidget
Công Nghệ

So sánh StatelessWidget và StatefulWidget

1. Intro Xin chào các bạn, đây là bài viết đầu tiên...

Các VS Code extension dành cho React Developer
Công Nghệ

Các VS Code extension dành cho React Developer

Với sự phát triển không ngừng của các công cụ và công...

Công Nghệ

Sự khác nhau giữa ngôn ngữ lập trình Python và C++

Python và C ++ là hai ngôn ngữ có các tính năng,...

Google Data Studio là gì? Hướng dẫn sử dụng Google Data Studio cho người mới
Công Nghệ

Google Data Studio là gì? Hướng dẫn sử dụng Google Data Studio cho người mới

Bạn có nhiều số liệu khô khan, bảng tính và các báo...