@iamkvnn/swagger-ts-codegen
v1.0.1
Published
Generate TypeScript types and Axios services from OpenAPI/Swagger specs
Maintainers
Readme
swagger-ts-codegen
Generate TypeScript types và Axios service functions từ OpenAPI/Swagger specs (JSON hoặc YAML).
Tính năng
- 🔄 YAML & JSON — hỗ trợ cả file
.json,.yaml/.ymlvà URL trực tiếp - 🧩 Generic detection — tự nhận
ApiResponseListX,ApiResponseX… →ApiResponse<T>và tách vàoshared.ts - 📁 Output linh hoạt — types và services có thể xuất ra folder riêng nhau
- 🏷️ Tag filter — chỉ generate những tag cần thiết (
--include-tags,--exclude-tags) - 📄 Per-tag files — tách mỗi service thành file riêng (
cart.service.ts,auth.service.ts…) - 🔤 Type prefix — thêm prefix vào tất cả type name (tránh conflict khi dùng nhiều service)
- ⚙️ Config file — quản lý nhiều microservices trong 1 file
codegen.config.json - 👁️ Watch mode — tự regenerate khi spec thay đổi (dành cho dev local)
- 🎛️ Enum style —
unionliterals hoặc TypeScriptenum - 📦 Params style — positional args hoặc destructured object (tương thích React Query)
Cài đặt
git clone <repo>
cd swagger-ts-codegen
npm installQuick Start
# Từ file JSON
node src/cli.js -i api.json -o src/generated
# Từ file YAML
node src/cli.js -i api.yaml -o src/generated
# Từ URL (Swagger UI đang chạy)
node src/cli.js -i http://localhost:8080/v3/api-docs -o src/generatedOutput Files
Mỗi lần generate tạo ra tối đa 3 loại file:
| File | Mô tả |
|------|-------|
| shared.ts | Generic wrapper types (ApiResponse<T>, PaginatedApiResponse<T>…) — không prefix, dùng chung |
| types.ts | Tất cả concrete types/interfaces/enums — có prefix nếu dùng --prefix |
| apiService.ts (hoặc {tag}.service.ts) | Axios instance + service objects theo từng tag |
shared.tschỉ được tạo khi spec có generic wrapper types.
Tham số CLI đầy đủ
📥 Input
| Flag | Short | Mô tả | Bắt buộc |
|------|-------|--------|----------|
| --input | -i | Đường dẫn file hoặc URL đến OpenAPI spec (JSON/YAML) | ✅ (trừ khi dùng --config) |
| --config | -c | Đường dẫn file config cho multi-service mode | ✅ (thay thế --input) |
📤 Output
| Flag | Short | Mô tả | Mặc định |
|------|-------|--------|----------|
| --output | -o | Thư mục output dự phòng cho tất cả file | ./generated |
| --types-dir | | Thư mục riêng cho file types | giá trị --output |
| --services-dir | | Thư mục riêng cho file service | giá trị --output |
| --service-filename | | Tên file service. Dùng {tag} để tách thành nhiều file | apiService.ts |
| --types-filename | | Tên file types (ví dụ: auth-types.ts) | types.ts |
| --shared-filename | | Tên file shared types | shared.ts |
| --ts-nocheck | | Thêm comment // @ts-nocheck và /* eslint-disable */ ở đầu các file | không |
Ví dụ --service-filename:
# Một file duy nhất
--service-filename "apiService.ts" # → apiService.ts
# Tách theo tag
--service-filename "{tag}.service.ts" # → cart.service.ts, auth.service.ts…
--service-filename "{tag}Api.ts" # → cartApi.ts, authApi.ts…🔗 Import paths (khi dùng folder riêng)
| Flag | Mô tả | Mặc định |
|------|--------|----------|
| --types-import | Import path cho types.ts trong file service | Tự tính tương đối |
| --shared-import | Import path cho shared.ts trong file service | Tự tính tương đối |
Khi nào cần dùng?
- Khi project có path alias (
@/,~) thay vì path tương đối - Khi deploy file service vào vị trí khác với lúc generate
# Với TypeScript path alias
node src/cli.js -i api.json \
--types-dir src/types \
--services-dir src/services \
--types-import "@/types/types" \
--shared-import "@/types/shared"🧬 Codegen
| Flag | Short | Giá trị | Mặc định | Mô tả |
|------|-------|---------|----------|-------|
| --enum | -e | union | enum | union | Kiểu enum |
| --params | -p | individual | object | individual | Kiểu tham số hàm service |
| --prefix | | string | (trống) | Prefix cho tất cả type names |
| --include-tags | | string (comma-sep) | (tất cả) | Chỉ generate những tag này |
| --exclude-tags | | string (comma-sep) | (không bỏ gì) | Bỏ qua những tag này |
Chi tiết --prefix:
- Chỉ apply cho concrete types (
CourseResponse→CinxCourseResponse) - Không apply cho generic wrappers (
ApiResponse<T>giữ nguyên) - Enum names cũng được prefix:
CourseResponseLevelEnum→CinxCourseResponseLevelEnum
Chi tiết --include-tags / --exclude-tags:
- So sánh không phân biệt hoa/thường, bỏ qua
-controllersuffix "cart"sẽ match tagcart-controller,Cart,cart
# Chỉ generate service cho cart và auth
--include-tags "cart,auth"
# Bỏ qua tag internal
--exclude-tags "internal,health,actuator"⚡ Axios
| Flag | Giá trị | Mặc định | Mô tả |
|------|---------|----------|-------|
| --axios-import | string | (tự tạo) | Path import axios client có sẵn. Khi dùng, không generate axios setup |
| --axios-client-name | string | apiClient | Tên biến axios client |
| --axios-client-import | named | default | named | Kiểu import |
Ví dụ dùng axios client có sẵn:
# Named import: import { apiClient } from '@/lib/apiClient'
node src/cli.js -i api.json \
--axios-import "@/lib/apiClient" \
--axios-client-name "apiClient" \
--axios-client-import "named"
# Default import: import myClient from '@/lib/apiClient'
node src/cli.js -i api.json \
--axios-import "@/lib/apiClient" \
--axios-client-name "myClient" \
--axios-client-import "default"🎮 Điều khiển
| Flag | Short | Mô tả |
|------|-------|-------|
| --only | | (chỉ với --config) Chỉ chạy một service theo name |
| --watch | | Bật watch mode — poll spec và regenerate khi thay đổi |
| --watch-interval | | Thời gian poll (ms). Mặc định: 3000 |
| --dry-run | | Preview output, không ghi file |
| --help | -h | Hiện help |
Enum Styles
--enum union (mặc định)
export interface CourseResponse {
level?: "BEGINNER" | "INTERMEDIATE" | "ADVANCED";
status?: "DRAFT" | "PUBLISHED" | "ARCHIVED";
}--enum enum
Enum được hoist thành named export riêng:
export enum CourseResponseLevelEnum {
BEGINNER = "BEGINNER",
INTERMEDIATE = "INTERMEDIATE",
ADVANCED = "ADVANCED",
}
export interface CourseResponse {
level?: CourseResponseLevelEnum;
}Params Styles
--params individual (mặc định)
async getCourses(
params?: { page?: number; size?: number; categoryId?: string },
config?: AxiosRequestConfig
): Promise<ApiResponse<CourseResponse[]>>
async updateCourse(
courseId: string,
body: UpdateCourseRequest,
config?: AxiosRequestConfig
): Promise<ApiResponse<CourseResponse>>--params object
Phù hợp với React Query và khi có nhiều tham số:
async getCourses(
{ page, size, categoryId }: { page?: number; size?: number; categoryId?: string },
config?: AxiosRequestConfig
): Promise<ApiResponse<CourseResponse[]>>
async updateCourse(
{ courseId, body }: { courseId: string; body: UpdateCourseRequest },
config?: AxiosRequestConfig
): Promise<ApiResponse<CourseResponse>>Watch Mode
Watch mode tự động theo dõi spec và regenerate khi nội dung thay đổi — không cần chạy lại lệnh thủ công.
Cách hoạt động:
- Load spec → tính MD5 hash → generate lần đầu
- Mỗi N giây: load lại spec → so sánh hash
- Nếu hash thay đổi → regenerate và in thông báo
- Nhấn
Ctrl+Cđể dừng
# Watch single spec
node src/cli.js -i http://localhost:8080/v3/api-docs -o src/generated --watch
# Watch với interval 5 giây
node src/cli.js -i api.json -o src/generated --watch --watch-interval 5000
# Watch tất cả services trong config
node src/cli.js --config codegen.config.json --watchConfig File (Microservices)
Khi project có nhiều microservice, dùng codegen.config.json để quản lý tất cả trong một chỗ.
Tạo config file
{
"services": [
{
"name": "auth",
"input": "https://api.example.com/v3/api-docs/auth",
"typesDir": "src/types/auth",
"servicesDir": "src/services/auth",
"typesImport": "@/types/auth/types",
"sharedImport": "@/types/auth/shared",
"axiosImport": "@/lib/apiClient",
"axiosClientName": "apiClient",
"paramsStyle": "object"
},
{
"name": "cart",
"input": "https://api.example.com/v3/api-docs/cart",
"typesDir": "src/types/cart",
"servicesDir": "src/services/cart",
"typesImport": "@/types/cart/types",
"sharedImport": "@/types/cart/shared",
"includeTags": ["cart"],
"paramsStyle": "individual"
}
],
"watchInterval": 3000
}Các field trong mỗi service
| Field | Bắt buộc | Mô tả |
|-------|----------|-------|
| name | ✅ | Tên định danh service (dùng với --only) |
| input | ✅ | File path hoặc URL đến OpenAPI spec |
| output | | Thư mục output dự phòng |
| typesDir | | Thư mục cho file types |
| servicesDir | | Thư mục cho file service |
| typesFilename | | Tên file types |
| sharedFilename | | Tên file shared types |
| typesImport | | Custom import path cho file types |
| sharedImport | | Custom import path cho file shared |
| serviceFilename | | Tên file service (hỗ trợ {tag}) |
| prefix | | Prefix cho type names |
| includeTags | | Mảng tag cần generate |
| excludeTags | | Mảng tag cần bỏ qua |
| enumStyle | | "union" hoặc "enum" |
| paramsStyle | | "individual" hoặc "object" |
| axiosImport | | Custom axios client import path |
| axiosClientName | | Tên biến axios client |
| axiosClientImport | | "named" hoặc "default" |
| tsNoCheck | | Boolean. Nếu true, thêm disable type-checking vào đầu file |
Chạy config
# Chạy tất cả services
node src/cli.js --config codegen.config.json
# Chỉ chạy một service
node src/cli.js --config codegen.config.json --only auth
# Xem trước, không ghi file
node src/cli.js --config codegen.config.json --dry-run
# Watch tất cả services
node src/cli.js --config codegen.config.json --watchVí dụ thực tế
Single service — cơ bản
node src/cli.js -i http://localhost:8080/v3/api-docs -o src/generatedSingle service — với path alias và separate folders
node src/cli.js \
-i http://localhost:8080/v3/api-docs \
--types-dir src/types \
--services-dir src/services \
--shared-import "@/types/shared" \
--types-import "@/types/types" \
--axios-import "@/lib/apiClient" \
--params objectPer-tag service files
node src/cli.js \
-i http://localhost:8080/v3/api-docs \
--service-filename "{tag}.service.ts" \
--types-dir src/types \
--services-dir src/services
# Tạo ra: cart.service.ts, auth.service.ts, course.service.ts…Prefix để tránh conflict (khi dùng nhiều service trong 1 project)
node src/cli.js -i http://auth-service/v3/api-docs -o src/generated/auth --prefix Auth
node src/cli.js -i http://cart-service/v3/api-docs -o src/generated/cart --prefix Cart
# AuthUserResponse, CartItemResponse — không bao giờ conflictGộp tất cả services và types vào chung folder (Flat folder layout)
Khi có nhiều microservice và bạn muốn tất cả các file code services được gen nằm chung trong thư mục src/services, còn tất cả các file types nằm chung trong thư mục src/types mà không bị ghi đè hay tạo thêm thư mục con, bạn chỉ định cụ thể các thuộc tính typesFilename, sharedFilename, và serviceFilename trong codegen.config.json như sau:
{
"services": [
{
"name": "auth",
"input": "https://api.example.com/v3/api-docs/auth",
"typesDir": "src/types",
"servicesDir": "src/services",
"typesFilename": "auth.types.ts",
"sharedFilename": "auth.shared.ts",
"serviceFilename": "auth.service.ts"
},
{
"name": "user",
"input": "https://api.example.com/v3/api-docs/user",
"typesDir": "src/types",
"servicesDir": "src/services",
"typesFilename": "user.types.ts",
"sharedFilename": "user.shared.ts",
"serviceFilename": "user.service.ts"
}
]
}Kết quả cấu trúc:
src/
├── services/
│ ├── auth.service.ts
│ └── user.service.ts
└── types/
├── auth.types.ts
├── auth.shared.ts
├── user.types.ts
└── user.shared.tsLưu ý: Bộ sinh code sẽ tự động tính toán relative imports động (ví dụ: trong auth.service.ts sẽ import từ ../types/auth.shared và ../types/auth.types) mà không cần khai báo typesImport hay sharedImport thủ công.
Programmatic API
const { generate, generateBatch } = require('./src/index');
// Single generate
const result = await generate('http://localhost:8080/v3/api-docs', {
outputDir: './src/generated',
enumStyle: 'union', // 'union' | 'enum'
paramsStyle: 'object', // 'individual' | 'object'
prefix: '', // '' | 'Auth' | 'Cart' | ...
includeTags: ['cart'], // [] = tất cả
excludeTags: [],
serviceFilename: 'apiService.ts', // hoặc '{tag}.service.ts'
typesFilename: 'types.ts', // tên file concrete types
sharedFilename: 'shared.ts', // tên file generic types
typesDir: null, // null = dùng outputDir
servicesDir: null,
typesImport: null, // null = tự tính
sharedImport: null,
dryRun: false,
axiosImport: null, // null = tự generate axios setup
axiosClientName: 'apiClient',
axiosClientImport: 'named',
tsNoCheck: false, // nếu true, chèn comment disabling check TS/eslint lên đầu file
});
// result.shared — nội dung shared.ts
// result.types — nội dung types.ts
// result.services — nội dung service(s) — string hoặc Map<tag, string>
// result.files — Array<{ path: string, content: string }>
// Batch generate từ config
const { loadConfig } = require('./src/config');
const config = loadConfig('./codegen.config.json');
const results = await generateBatch(config, { dryRun: false });Generic Detection
Wrapper schemas được tự động nhận diện theo tên và gom thành generic interface trong shared.ts:
| Schema name trong spec | Được resolve thành |
|------------------------|-------------------|
| ApiResponseListFoo | ApiResponse<Foo[]> |
| ApiResponseFoo | ApiResponse<Foo> |
| PaginatedApiResponseFoo | PaginatedApiResponse<Foo> |
shared.ts (không prefix):
export interface ApiResponse<T> {
success?: boolean;
message?: string;
data?: T;
}types.ts (có prefix nếu dùng --prefix):
export interface CourseResponse { ... }
export interface CartItemResponse { ... }service file import từ cả 2:
import type { ApiResponse } from './shared';
import type { CourseResponse, CartItemResponse } from './types';