OpenAPI-First #5: Contract Check tự động với tsc và Husky
Làm thế nào để đảm bảo controller luôn implement đúng và đủ interface từ spec? Bài này hướng dẫn setup contract-check.ts và tích hợp vào pre-commit hook và CI.
Nghe bài viết này dưới dạng podcastBạn đã có Interface từ spec package, controller đã implements interface — nhưng làm thế nào để đảm bảo không ai quên implement một method mới khi spec thay đổi?
Giải pháp: một file contract-check.ts duy nhất, kết hợp với tsc --noEmit chạy ở pre-commit và CI.
Ý tưởng cốt lõi
TypeScript compiler là tool check contract tốt nhất
→ Tạo một file ép compiler phải check
→ Chạy tsc --noEmit ở pre-commit + CI
→ Không cần tool thêm, không cần custom script
Tạo contract-check.ts
// src/contract-check.ts
// ⚠️ File này CHỈ để TypeScript check — không import vào app
// Mỗi controller phải có một entry ở đây
import {
IUsersController,
IAuthController,
IProductsController,
} from '@your-org/api-contracts-nestjs';
import { UsersController } from './features/users/users.controller';
import { AuthController } from './features/auth/auth.controller';
import { ProductsController } from './features/products/products.controller';
// Ép TypeScript kiểm tra assignability
// Nếu controller thiếu method hoặc sai signature → lỗi compile ngay đây
const _checks: [
IUsersController,
IAuthController,
IProductsController,
] = [
new UsersController(null as any),
new AuthController(null as any),
new ProductsController(null as any),
];
void _checks; // suppress unused warning
Lỗi trông như thế nào
Khi thiếu method:
src/contract-check.ts:18:3 - error TS2322:
Type 'UsersController' is not assignable to type 'IUsersController'.
Property 'bulkDeleteUsers' is missing in type 'UsersController'
but required in type 'IUsersController'.
Khi sai signature:
src/contract-check.ts:18:3 - error TS2322:
Type 'UsersController' is not assignable to type 'IUsersController'.
Types of property 'listUsers' are incompatible.
Type '(page: string) => Promise<UserListResponse>' is not assignable to
Type '(page: number) => Promise<UserListResponse>'.
Type 'string' is not assignable to type 'number'.
Lỗi chỉ thẳng vào file và dòng cụ thể — developer biết ngay phải fix gì.
Setup pre-commit hook với Husky
# Cài một lần
pnpm add -D husky
npx husky init
# .husky/pre-commit
#!/bin/sh
echo "🔍 Checking API contracts..."
pnpm contract:check
if [ $? -ne 0 ]; then
echo "❌ Contract check failed — fix before committing"
exit 1
fi
echo "✅ Contract check passed"
// package.json
{
"scripts": {
"contract:check": "tsc --noEmit",
"prepare": "husky"
}
}
Sau khi chạy pnpm install, Husky tự setup. Từ đó mỗi git commit đều chạy contract check trước.
Setup CI
# .github/workflows/contract-check.yml
name: Contract Check
on:
pull_request:
paths:
- 'src/**'
- 'package.json' # khi update version package mới
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://npm.pkg.github.com
- run: pnpm install
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Contract check
run: pnpm contract:check
Quy trình đầy đủ khi có breaking change
1. api-spec thêm method mới vào IUsersController
↓
2. CI publish package version mới (dev tag)
↓
3. NestJS repo: pnpm install
↓
4. Dev thử git commit
→ Husky chạy tsc --noEmit
→ Báo lỗi: method missing
→ Commit bị block
↓
5. Dev thêm method vào UsersController
↓
6. git commit thành công
↓
7. Push → CI contract check pass → PR mergeable
Mỗi khi thêm controller mới
Quy tắc: thêm controller mới phải thêm vào contract-check.ts. Có thể enforce bằng cách thêm comment rõ ràng:
// src/contract-check.ts
//
// QUY TẮC: Mỗi controller mới PHẢI được thêm vào đây
// trước khi merge PR. Đây là cơ chế duy nhất đảm bảo
// contract được check tự động.
//
Hoặc thêm vào PR checklist của team:
- Nếu có controller mới, đã thêm vào
src/contract-check.ts
Kết luận
contract-check.tslà file duy nhất cần tạo — không cần tool hay script phức tạp.tsc --noEmitlàm toàn bộ việc kiểm tra — TypeScript compiler là contract checker tốt nhất.- Pre-commit hook block commit khi contract sai — developer biết ngay, không chờ CI.
- CI check đảm bảo không ai bypass bằng cách dùng
--no-verify. - Mỗi controller mới phải được thêm vào
contract-check.ts— đây là quy tắc team cần enforce qua code review.
Chưa có bình luận
Để lại bình luận
Bình luận sẽ được phê duyệt trước khi hiển thị.