Phát triển API giao dịch với Deno và TypeScript
2024-03-03 — QuantTrade
Ngày đăng: 15 Tháng 6, 2023 | Thời gian đọc: 15 phút
Hướng dẫn toàn diện về cách xây dựng, triển khai và duy trì các API giao dịch hiệu quả sử dụng Deno runtime và sức mạnh của TypeScript.
Giới thiệu
Trong thế giới phát triển web hiện đại, việc xây dựng API mạnh mẽ, an toàn và có hiệu suất cao là rất quan trọng. Với sự xuất hiện của Deno - một runtime JavaScript và TypeScript hiện đại, chúng ta có thêm một công cụ mạnh mẽ cho việc phát triển API.
Bài viết này sẽ hướng dẫn bạn cách xây dựng API giao dịch sử dụng Deno và TypeScript, với trọng tâm vào bảo mật, hiệu suất và khả năng bảo trì.
Deno là gì?
Deno là một runtime JavaScript và TypeScript đơn giản, hiện đại và bảo mật được tạo ra bởi Ryan Dahl - người sáng tạo ra Node.js. Deno được xây dựng trên V8 JavaScript engine và Rust, mang lại hiệu suất tuyệt vời và bảo mật mặc định.
Đặc điểm chính của Deno:
- Bảo mật mặc định - không có quyền truy cập vào hệ thống tệp, mạng hoặc môi trường trừ khi được cấp phép rõ ràng
- Hỗ trợ TypeScript mà không cần cấu hình
- Sử dụng URL để nhập modules
- Chỉ có một tệp thực thi duy nhất
- Có sẵn các công cụ như trình định dạng, linter, và trình kiểm tra
- Có thể tạo ra một tệp thực thi duy nhất từ các modules JavaScript
TypeScript Overview
TypeScript là một superset của JavaScript giúp đảm bảo an toàn kiểu dữ liệu và cải thiện trải nghiệm phát triển thông qua:
- Kiểm tra kiểu dữ liệu tĩnh
- Autocompletion và IntelliSense tốt hơn trong IDE
- Khả năng phát hiện lỗi sớm hơn
- Khả năng bảo trì và mở rộng code tốt hơn

Dưới đây là một ví dụ đơn giản về TypeScript trong Deno:
// interfaces.ts
export interface Transaction {
id: string;
amount: number;
currency: string;
description?: string;
timestamp: Date;
status: 'pending' | 'completed' | 'failed';
}
// transaction.ts
import { Transaction } from "./interfaces.ts";
function processTransaction(transaction: Transaction): Promise<Transaction> {
// Process the transaction
return new Promise((resolve, reject) => {
// Validation logic
if (transaction.amount <= 0) {
return reject(new Error("Transaction amount must be positive"));
}
// Processing logic
const processedTransaction: Transaction = {
...transaction,
status: 'completed',
timestamp: new Date()
};
resolve(processedTransaction);
});
}
// Example usage
const transaction: Transaction = {
id: "txn_123456",
amount: 100.50,
currency: "USD",
description: "Payment for services",
timestamp: new Date(),
status: 'pending'
};
processTransaction(transaction)
.then(result => console.log("Transaction processed:", result))
.catch(error => console.error("Error processing transaction:", error));
Cài đặt môi trường
Để bắt đầu với Deno và TypeScript, bạn cần cài đặt Deno runtime trên máy của mình.
Cài đặt Deno
Trên macOS, Linux, hoặc WSL:
# Cài đặt bằng curl
curl -fsSL https://deno.land/install.sh | sh
# Hoặc bằng Homebrew trên macOS
brew install deno
Trên Windows:
# Cài đặt bằng PowerShell
iwr https://deno.land/install.ps1 -useb | iex
# Hoặc bằng Chocolatey
choco install deno
# Hoặc bằng Scoop
scoop install deno
Cấu trúc dự án
Một cấu trúc dự án API Deno điển hình có thể như sau:
transaction-api/
├── .vscode/ # VS Code configuration
│ └── settings.json # Editor settings for Deno
├── controllers/ # Request controllers
│ └── transaction.ts
├── models/ # Data models
│ └── transaction.ts
├── routes/ # API routes
│ └── transaction.ts
├── services/ # Business logic
│ └── transaction.ts
├── utils/ # Utility functions
│ └── helpers.ts
├── middlewares/ # Custom middlewares
│ ├── auth.ts
│ └── error.ts
├── deps.ts # External dependencies
├── app.ts # Main application
├── config.ts # Configuration
└── deno.json # Deno project configuration
Cơ bản về API
Deno cung cấp một số module tiêu chuẩn để xây dựng API web. Để xây dựng API, chúng ta có thể sử dụng module std/http hoặc các framework web như Oak, ABC, hoặc Drash.
Trong ví dụ này, chúng ta sẽ sử dụng Oak - một middleware framework lấy cảm hứng từ Koa.
Thiết lập cơ bản với Oak
// deps.ts
export { Application, Router } from "https://deno.land/x/oak/mod.ts";
export type { Context, Next } from "https://deno.land/x/oak/mod.ts";
// app.ts
import { Application } from "./deps.ts";
import transactionRouter from "./routes/transaction.ts";
const app = new Application();
const PORT = Deno.env.get("PORT") ? Number(Deno.env.get("PORT")) : 8000;
// Logger middleware
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.request.method} ${ctx.request.url.pathname} - ${ms}ms`);
});
// Error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.response.status = err.status || 500;
ctx.response.body = {
success: false,
message: err.message
};
console.error(err);
}
});
// Router
app.use(transactionRouter.routes());
app.use(transactionRouter.allowedMethods());
// Start server
console.log(`Server running on http://localhost:${PORT}`);
await app.listen({ port: PORT });
Xây dựng RESTful API
Bây giờ chúng ta sẽ xây dựng API giao dịch RESTful. Đầu tiên, hãy định nghĩa model giao dịch:
// models/transaction.ts
export interface Transaction {
id: string;
userId: string;
amount: number;
currency: string;
type: 'deposit' | 'withdrawal' | 'transfer';
status: 'pending' | 'completed' | 'failed';
description?: string;
metadata?: Record<string, unknown>;
createdAt: Date;
updatedAt: Date;
}
export interface CreateTransactionDto {
userId: string;
amount: number;
currency: string;
type: 'deposit' | 'withdrawal' | 'transfer';
description?: string;
metadata?: Record<string, unknown>;
}
Tiếp theo, tạo một service để xử lý logic nghiệp vụ:
// services/transaction.ts
import { Transaction, CreateTransactionDto } from "../models/transaction.ts";
import { v4 as uuid } from "https://deno.land/std/uuid/mod.ts";
// In-memory storage for this example
const transactions: Transaction[] = [];
export class TransactionService {
static getAllTransactions(): Transaction[] {
return transactions;
}
static getTransactionById(id: string): Transaction | undefined {
return transactions.find(t => t.id === id);
}
static getTransactionsByUserId(userId: string): Transaction[] {
return transactions.filter(t => t.userId === userId);
}
static createTransaction(dto: CreateTransactionDto): Transaction {
const now = new Date();
const transaction: Transaction = {
id: uuid(),
...dto,
status: 'pending',
createdAt: now,
updatedAt: now
};
transactions.push(transaction);
return transaction;
}
static updateTransactionStatus(id: string, status: 'pending' | 'completed' | 'failed'): Transaction | undefined {
const transaction = this.getTransactionById(id);
if (!transaction) return undefined;
transaction.status = status;
transaction.updatedAt = new Date();
return transaction;
}
}
Sau đó, tạo một controller để xử lý các yêu cầu HTTP:
// controllers/transaction.ts
import { Context } from "../deps.ts";
import { TransactionService } from "../services/transaction.ts";
import { CreateTransactionDto } from "../models/transaction.ts";
export class TransactionController {
static async getAll(ctx: Context) {
try {
const transactions = TransactionService.getAllTransactions();
ctx.response.body = { success: true, data: transactions };
} catch (error) {
ctx.response.status = 500;
ctx.response.body = { success: false, message: error.message };
}
}
static async getById(ctx: Context) {
try {
const id = ctx.params?.id;
if (!id) {
ctx.response.status = 400;
ctx.response.body = { success: false, message: "Transaction ID is required" };
return;
}
const transaction = TransactionService.getTransactionById(id);
if (!transaction) {
ctx.response.status = 404;
ctx.response.body = { success: false, message: "Transaction not found" };
return;
}
ctx.response.body = { success: true, data: transaction };
} catch (error) {
ctx.response.status = 500;
ctx.response.body = { success: false, message: error.message };
}
}
static async create(ctx: Context) {
try {
const body = await ctx.request.body();
if (!body.value) {
ctx.response.status = 400;
ctx.response.body = { success: false, message: "Request body is required" };
return;
}
const dto: CreateTransactionDto = body.value;
const transaction = TransactionService.createTransaction(dto);
ctx.response.status = 201;
ctx.response.body = { success: true, data: transaction };
} catch (error) {
ctx.response.status = 500;
ctx.response.body = { success: false, message: error.message };
}
}
static async updateStatus(ctx: Context) {
try {
const id = ctx.params?.id;
if (!id) {
ctx.response.status = 400;
ctx.response.body = { success: false, message: "Transaction ID is required" };
return;
}
const body = await ctx.request.body();
if (!body.value || !body.value.status) {
ctx.response.status = 400;
ctx.response.body = { success: false, message: "Status is required" };
return;
}
const { status } = body.value;
const transaction = TransactionService.updateTransactionStatus(id, status);
if (!transaction) {
ctx.response.status = 404;
ctx.response.body = { success: false, message: "Transaction not found" };
return;
}
ctx.response.body = { success: true, data: transaction };
} catch (error) {
ctx.response.status = 500;
ctx.response.body = { success: false, message: error.message };
}
}
}
Cuối cùng, hãy thiết lập các route:
// routes/transaction.ts
import { Router } from "../deps.ts";
import { TransactionController } from "../controllers/transaction.ts";
const router = new Router();
router
.get('/api/transactions', TransactionController.getAll)
.get('/api/transactions/:id', TransactionController.getById)
.post('/api/transactions', TransactionController.create)
.patch('/api/transactions/:id/status', TransactionController.updateStatus);
export default router;
Xác thực và bảo mật
Xác thực và bảo mật là rất quan trọng cho các API giao dịch. Dưới đây là một ví dụ về middleware xác thực JWT đơn giản:
// middlewares/auth.ts
import { Context, Next } from "../deps.ts";
import { create, verify } from "https://deno.land/x/djwt@v2.8/mod.ts";
const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-512" },
true,
["sign", "verify"]
);
export async function generateToken(payload: Record<string, unknown>) {
return await create({ alg: "HS512", typ: "JWT" }, payload, key);
}
export async function authMiddleware(ctx: Context, next: Next) {
try {
const authHeader = ctx.request.headers.get('Authorization');
if (!authHeader) {
ctx.response.status = 401;
ctx.response.body = { success: false, message: "No authorization token provided" };
return;
}
const token = authHeader.split(' ')[1];
if (!token) {
ctx.response.status = 401;
ctx.response.body = { success: false, message: "Invalid token format" };
return;
}
const payload = await verify(token, key);
ctx.state.user = payload;
await next();
} catch (error) {
ctx.response.status = 401;
ctx.response.body = { success: false, message: "Invalid token" };
}
}
Cập nhật route để sử dụng middleware xác thực:
// routes/transaction.ts
import { Router } from "../deps.ts";
import { TransactionController } from "../controllers/transaction.ts";
import { authMiddleware } from "../middlewares/auth.ts";
const router = new Router();
// Public routes
router.get('/api/health', (ctx) => {
ctx.response.body = { status: 'ok' };
});
// Protected routes
router
.get('/api/transactions', authMiddleware, TransactionController.getAll)
.get('/api/transactions/:id', authMiddleware, TransactionController.getById)
.post('/api/transactions', authMiddleware, TransactionController.create)
.patch('/api/transactions/:id/status', authMiddleware, TransactionController.updateStatus);
export default router;
Tích hợp cơ sở dữ liệu
Deno có thể tích hợp với nhiều cơ sở dữ liệu như MongoDB, PostgreSQL, MySQL, và SQLite. Dưới đây là ví dụ với MongoDB:
// db/mongodb.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { config } from "https://deno.land/x/dotenv/mod.ts";
const env = await config();
const MONGO_URI = env.MONGO_URI || "mongodb://localhost:27017";
const DB_NAME = env.DB_NAME || "transaction_api";
// Connect to MongoDB
const client = new MongoClient();
await client.connect(MONGO_URI);
const db = client.database(DB_NAME);
// Define collections
export const transactionsCollection = db.collection("transactions");
export const usersCollection = db.collection("users");
// Connect and set up indexes
export async function setupDatabase() {
try {
// Create indexes for better performance
await transactionsCollection.createIndexes({
indexes: [
{
key: { userId: 1 },
name: "userId_index"
},
{
key: { status: 1 },
name: "status_index"
},
{
key: { createdAt: -1 },
name: "createdAt_index"
}
]
});
console.log("Database setup completed successfully");
} catch (error) {
console.error("Database setup failed:", error);
throw error;
}
}
Và cập nhật service để sử dụng MongoDB:
// services/transaction.ts
import { Transaction, CreateTransactionDto } from "../models/transaction.ts";
import { transactionsCollection } from "../db/mongodb.ts";
import { v4 as uuid } from "https://deno.land/std/uuid/mod.ts";
export class TransactionService {
static async getAllTransactions(): Promise<Transaction[]> {
return await transactionsCollection.find().toArray();
}
static async getTransactionById(id: string): Promise<Transaction | null> {
return await transactionsCollection.findOne({ id });
}
static async getTransactionsByUserId(userId: string): Promise<Transaction[]> {
return await transactionsCollection.find({ userId }).toArray();
}
static async createTransaction(dto: CreateTransactionDto): Promise<Transaction> {
const now = new Date();
const transaction: Transaction = {
id: uuid(),
...dto,
status: 'pending',
createdAt: now,
updatedAt: now
};
await transactionsCollection.insertOne(transaction);
return transaction;
}
static async updateTransactionStatus(id: string, status: 'pending' | 'completed' | 'failed'): Promise<Transaction | null> {
const transaction = await this.getTransactionById(id);
if (!transaction) return null;
const updatedTransaction: Transaction = {
...transaction,
status,
updatedAt: new Date()
};
await transactionsCollection.updateOne(
{ id },
{ $set: { status, updatedAt: updatedTransaction.updatedAt }}
);
return updatedTransaction;
}
}
Kiểm thử API
Kiểm thử là một phần quan trọng trong quá trình phát triển API. Deno có sẵn các công cụ kiểm thử mà không cần cài đặt thêm. Dưới đây là các ví dụ kiểm thử đơn vị và tích hợp:
// tests/transaction_service_test.ts
import { assertEquals, assertExists } from "https://deno.land/std/testing/asserts.ts";
import { TransactionService } from "../services/transaction.ts";
Deno.test("TransactionService - createTransaction", async () => {
// Given
const dto = {
userId: "user123",
amount: 100,
currency: "USD",
type: "deposit" as const,
description: "Test deposit"
};
// When
const transaction = await TransactionService.createTransaction(dto);
// Then
assertExists(transaction.id);
assertEquals(transaction.amount, 100);
assertEquals(transaction.currency, "USD");
assertEquals(transaction.type, "deposit");
assertEquals(transaction.status, "pending");
assertEquals(transaction.description, "Test deposit");
assertExists(transaction.createdAt);
assertExists(transaction.updatedAt);
});
Deno.test("TransactionService - updateTransactionStatus", async () => {
// Given
const dto = {
userId: "user123",
amount: 100,
currency: "USD",
type: "deposit" as const
};
const transaction = await TransactionService.createTransaction(dto);
// When
const updatedTransaction = await TransactionService.updateTransactionStatus(transaction.id, "completed");
// Then
assertExists(updatedTransaction);
assertEquals(updatedTransaction!.id, transaction.id);
assertEquals(updatedTransaction!.status, "completed");
});
Và kiểm thử tích hợp với SuperDeno:
// tests/transaction_api_test.ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
import { superoak } from "https://deno.land/x/superoak@4.7.0/mod.ts";
import app from "../app.ts";
Deno.test("POST /api/transactions - should create a transaction", async () => {
const request = await superoak(app);
const response = await request
.post("/api/transactions")
.set("Authorization", "Bearer your-test-token")
.send({
userId: "user123",
amount: 100,
currency: "USD",
type: "deposit",
description: "Test deposit"
});
assertEquals(response.status, 201);
assertEquals(response.body.success, true);
assertEquals(response.body.data.amount, 100);
assertEquals(response.body.data.status, "pending");
});
Deno.test("GET /api/transactions/:id - should get a transaction by id", async () => {
// First create a transaction
const createRequest = await superoak(app);
const createResponse = await createRequest
.post("/api/transactions")
.set("Authorization", "Bearer your-test-token")
.send({
userId: "user123",
amount: 100,
currency: "USD",
type: "deposit"
});
const transactionId = createResponse.body.data.id;
// Then get the transaction by id
const getRequest = await superoak(app);
const getResponse = await getRequest
.get(`/api/transactions/${transactionId}`)
.set("Authorization", "Bearer your-test-token");
assertEquals(getResponse.status, 200);
assertEquals(getResponse.body.success, true);
assertEquals(getResponse.body.data.id, transactionId);
});
Triển khai ứng dụng
Triển khai Deno API có thể thực hiện thông qua nhiều dịch vụ như Deno Deploy, Docker, hoặc các dịch vụ lưu trữ truyền thống.
Triển khai với Deno Deploy
Deno Deploy là một dịch vụ lưu trữ serverless được tối ưu hóa cho ứng dụng Deno:
- Đăng ký tài khoản tại Deno Deploy
- Tạo một dự án mới
- Kết nối với GitHub repository của bạn
- Cấu hình deployments để chạy file entry của bạn (thường là app.ts)
Triển khai với Docker
# Dockerfile
FROM denoland/deno:latest
WORKDIR /app
# Caching dependencies
COPY deps.ts .
RUN deno cache deps.ts
# Copying source files
COPY . .
# Compile the app
RUN deno cache app.ts
# Port configuration
EXPOSE 8000
# Run with required permissions
CMD ["run", "--allow-net", "--allow-env", "--allow-read", "app.ts"]
Và docker-compose.yml cho triển khai đầy đủ:
version: '3'
services:
api:
build: .
ports:
- "8000:8000"
environment:
- PORT=8000
- MONGO_URI=mongodb://mongo:27017
- DB_NAME=transaction_api
depends_on:
- mongo
mongo:
image: mongo:latest
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
volumes:
mongo_data:
Các phương pháp tốt nhất
Bảo mật
- Luôn sử dụng HTTPS trong môi trường sản xuất
- Triển khai xác thực và ủy quyền đúng cách
- Sử dụng các phương pháp mã hóa an toàn cho dữ liệu nhạy cảm
- Giới hạn quyền Deno chỉ cho những gì cần thiết (--allow-net, --allow-read, v.v.)
- Thực hiện xác thực đầu vào nghiêm ngặt
Hiệu suất
- Sử dụng các kỹ thuật caching khi thích hợp
- Tối ưu hóa truy vấn cơ sở dữ liệu và chỉ mục
- Sử dụng các luồng worker để xử lý các tác vụ nặng
- Tận dụng các tính năng bất đồng bộ của Deno
Khả năng mở rộng
- Thiết kế API có thể mở rộng từ đầu
- Sử dụng kiến trúc microservice khi có thể
- Triển khai cân bằng tải
- Xem xét sử dụng cơ sở dữ liệu phân tán
Kiểm tra và giám sát
- Viết các kiểm thử đơn vị và tích hợp toàn diện
- Triển khai các công cụ theo dõi và thông báo
- Sử dụng ghi nhật ký có cấu trúc
- Thiết lập các kiểm tra sức khỏe API
Kết luận
Trong bài viết này, chúng ta đã khám phá cách xây dựng API giao dịch bảo mật và hiệu quả sử dụng Deno và TypeScript. Chúng ta đã bao gồm tất cả các khía cạnh từ thiết lập môi trường phát triển, xây dựng API RESTful, xác thực, tích hợp cơ sở dữ liệu, kiểm thử đến triển khai.
Deno cung cấp một môi trường hiện đại và bảo mật để phát triển API, trong khi TypeScript giúp tăng tính an toàn và khả năng bảo trì của code. Kết hợp cả hai công nghệ này, chúng ta có thể xây dựng các API giao dịch mạnh mẽ, an toàn và có khả năng mở rộng.
Để tìm hiểu thêm, hãy khám phá các tài liệu chính thức của Deno tại https://deno.land/manual và TypeScript tại https://www.typescriptlang.org/docs.