Adding Swagger to Existing Charcole Projects
Adding Swagger to older Charcole projects
If you created a project with Charcole v2.1 or earlier, you do not have the auto-generated Swagger feature.
Your project still works perfectly. Nothing is broken. But you are missing out on automatic documentation.
This guide shows how to add @charcoles/swagger to your existing project and eliminate schema duplication.
The process takes about 10 minutes.
What you will gain
Before adding @charcoles/swagger, you either:
- Have no API documentation at all
- Manually write Swagger YAML that duplicates your Zod schemas
- Use external tools that are disconnected from your validation
After adding @charcoles/swagger:
- Schemas defined once in Zod
- Documentation auto-generated from those schemas
- Zero duplication
- Always in sync
- 60-75% less documentation overhead
If your project has 20 endpoints, this will save you around 1,000 lines of duplicated YAML.
Step 1: Install the package
npm install @charcoles/swagger
That's the only dependency you need.
You already have Zod (it comes with all Charcole projects). You already have Express. You already have the schemas.
Just add @charcoles/swagger.
Step 2: Create Swagger configuration
Create a new file to configure Swagger.
For TypeScript projects:
mkdir -p src/config
touch src/config/swagger.config.ts
For JavaScript projects:
mkdir -p src/config
touch src/config/swagger.config.js
Step 3: Configure Swagger with your schemas
TypeScript version (src/config/swagger.config.ts):
const swaggerConfig = {
title: process.env.APP_NAME || "My API",
version: process.env.APP_VERSION || "1.0.0",
description: "Auto-generated API documentation",
path: "/api-docs",
servers: [
{
url: `http://localhost:${process.env.PORT || 3000}`,
description: "Development server",
},
],
// Register your Zod schemas here
schemas: {
// Example - import your actual schemas:
// createUserSchema,
// updateUserSchema,
// loginSchema,
},
includeCommonResponses: true, // Success, ValidationError, etc.
};
export default swaggerConfig;
JavaScript version (src/config/swagger.config.js):
const swaggerConfig = {
title: process.env.APP_NAME || "My API",
version: process.env.APP_VERSION || "1.0.0",
description: "Auto-generated API documentation",
path: "/api-docs",
servers: [
{
url: `http://localhost:${process.env.PORT || 3000}`,
description: "Development server",
},
],
schemas: {
// Import and register your schemas here
},
includeCommonResponses: true,
};
export default swaggerConfig;
Step 4: Import your Zod schemas
Find where your validation schemas are defined.
Typical locations in Charcole projects:
src/modules/auth/auth.schemas.ts(if you have auth)src/modules/*/schemas.ts(module-specific schemas)src/schemas/(if you created a schemas folder)
Import them into your Swagger config:
TypeScript:
// src/config/swagger.config.ts
import { registerSchema, loginSchema } from "../modules/auth/auth.schemas.ts";
import {
createItemSchema,
updateItemSchema,
} from "../modules/items/items.schemas.ts";
const swaggerConfig = {
// ... other config
schemas: {
registerSchema,
loginSchema,
createItemSchema,
updateItemSchema,
},
};
JavaScript:
// src/config/swagger.config.js
import { registerSchema, loginSchema } from "../modules/auth/auth.schemas.js";
import {
createItemSchema,
updateItemSchema,
} from "../modules/items/items.schemas.js";
const swaggerConfig = {
// ... other config
schemas: {
registerSchema,
loginSchema,
createItemSchema,
updateItemSchema,
},
};
Step 5: Setup Swagger in your app
Open your main app file (usually src/app.ts or src/app.js).
Add these imports at the top:
import { setupSwagger } from "@charcoles/swagger";
import swaggerOptions from "./config/swagger.config.js"; // or .ts
Then call setupSwagger() after you create your Express app but before you define routes:
TypeScript (src/app.ts):
import express from "express";
import cors from "cors";
import { setupSwagger } from "@charcoles/swagger";
import swaggerOptions from "./config/swagger.config.ts";
import { errorHandler } from "./middleware/errorHandler.ts";
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
// Setup Swagger (before routes!)
setupSwagger(app, swaggerOptions);
// Routes
app.use("/api/health", healthRoutes);
app.use("/api/auth", authRoutes);
// ... your other routes
// Error handler (always last)
app.use(errorHandler);
export default app;
JavaScript (src/app.js):
import express from "express";
import cors from "cors";
import { setupSwagger } from "@charcoles/swagger";
import swaggerOptions from "./config/swagger.config.js";
import { errorHandler } from "./middleware/errorHandler.js";
const app = express();
app.use(cors());
app.use(express.json());
setupSwagger(app, swaggerOptions);
app.use("/api/health", healthRoutes);
app.use("/api/auth", authRoutes);
app.use(errorHandler);
export default app;
Step 6: Test it works
Start your server:
npm run dev
Visit:
http://localhost:3000/api-docs
You should see Swagger UI with:
- Your API title and description
- All registered schemas in the "Schemas" section
- Built-in response templates (Success, ValidationError, etc.)
If you see this, configuration is successful.
Step 7: Document your endpoints
Now instead of writing full OpenAPI schemas manually, you use $ref.
Example - Before @charcoles/swagger:
/**
* @swagger
* /api/auth/register:
* post:
* summary: Register a new user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* properties:
* name:
* type: string
* minLength: 1
* email:
* type: string
* format: email
* password:
* type: string
* minLength: 8
* responses:
* 201:
* description: User created successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* ... 30 more lines
*/
After @charcoles/swagger:
/**
* @swagger
* /api/auth/register:
* post:
* summary: Register a new user
* tags:
* - Authentication
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/registerSchema'
* responses:
* 201:
* $ref: '#/components/responses/Success'
* 400:
* $ref: '#/components/responses/ValidationError'
*/
76 lines → 16 lines. Same documentation, 80% less code.
Step 8: Update all your endpoints
Go through your route files and replace manual schemas with $ref.
Pattern:
// Old
schema:
type: object
properties:
field: { type: string }
...
// New
schema:
$ref: '#/components/schemas/yourSchemaName'
Pattern for responses:
// Old
responses:
200:
description: Success
content:
application/json:
schema:
type: object
...
// New
responses:
200:
$ref: '#/components/responses/Success'
400:
$ref: '#/components/responses/ValidationError'
Built-in response templates
These are automatically available after setup:
| Response Name | Status | Use Case |
|---|---|---|
Success | 200 | Successful GET/POST/PUT |
ValidationError | 400 | Invalid request body |
Unauthorized | 401 | Missing/invalid token |
Forbidden | 403 | Insufficient permissions |
NotFound | 404 | Resource not found |
InternalError | 500 | Server error |
Use them with $ref:
responses:
200:
$ref: "#/components/responses/Success"
401:
$ref: "#/components/responses/Unauthorized"
404:
$ref: "#/components/responses/NotFound"
If you have authentication endpoints
Charcole v2.1+ with auth module:
Your auth schemas are already defined in src/modules/auth/auth.schemas.ts.
Just import and register them:
// src/config/swagger.config.ts
import { registerSchema, loginSchema } from "../modules/auth/auth.schemas.ts";
const swaggerConfig = {
schemas: {
registerSchema,
loginSchema,
},
};
Then update your auth route documentation:
// src/modules/auth/auth.routes.ts
/**
* @swagger
* /api/auth/login:
* post:
* summary: Login with email and password
* tags:
* - Authentication
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/loginSchema'
* responses:
* 200:
* $ref: '#/components/responses/Success'
* 400:
* $ref: '#/components/responses/ValidationError'
* 401:
* $ref: '#/components/responses/Unauthorized'
*/
Done. Authentication fully documented.
Migration checklist
- Install
@charcoles/swagger - Create
src/config/swagger.config.ts(or.js) - Import all your Zod schemas into the config
- Register schemas in the
schemasobject - Add
setupSwagger(app, swaggerOptions)to your app file - Test Swagger UI at
/api-docs - Update route documentation to use
$ref - Replace manual response schemas with built-in templates
- Restart server and verify all endpoints appear correctly
Common issues
Schemas not appearing in Swagger UI?
- Check spelling - schema names must match exactly
- Verify imports - make sure schemas are actually imported
- Restart server - changes to config require restart
$ref not resolving?
Make sure the schema name in $ref matches the key in your config:
// Config
schemas: {
createUserSchema, // Key is "createUserSchema"
}
// Usage
$ref: '#/components/schemas/createUserSchema' // Must match
TypeScript errors after adding Swagger?
Make sure you imported types correctly:
import { setupSwagger } from "@charcoles/swagger";
import type { SwaggerOptions } from "@charcoles/swagger";
const swaggerOptions: SwaggerOptions = {
// ...
};
Gradual migration approach
You do not have to update everything at once.
Phase 1: Setup Swagger with one schema
- Install package
- Configure with a single schema
- Update one endpoint
- Verify it works
Phase 2: Add more schemas gradually
- Add schemas one module at a time
- Update documentation incrementally
- Keep old manual docs until replacement is complete
Phase 3: Replace all manual schemas
- Go through all routes
- Replace manual YAML with
$ref - Use built-in response templates
- Delete old duplicated schemas
Take your time. Old and new approaches coexist peacefully.
What if I have custom response formats?
You can define custom responses in the config:
const swaggerConfig = {
schemas: {
/* ... */
},
customResponses: {
UserWithToken: {
description: "User data with JWT token",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean" },
token: { type: "string" },
user: {
type: "object",
properties: {
id: { type: "string" },
email: { type: "string" },
},
},
},
},
},
},
},
},
};
Then use it:
responses:
201:
$ref: "#/components/responses/UserWithToken"
Testing your documentation
After migration, test thoroughly:
- Start server:
npm run dev - Open Swagger UI:
http://localhost:3000/api-docs - Check schemas section: All registered schemas should appear
- Check each endpoint: Verify request/response schemas display correctly
- Try "Try it out": Test endpoints directly from Swagger UI
- Check responses: Make sure response templates are correct
Final thoughts
Adding @charcoles/swagger to an existing Charcole project is straightforward.
You already have Zod schemas. You already have validation. You just register those schemas once and reference them.
The result is less code, better documentation, and zero duplication.
If you have 20 endpoints, expect to save 1,000+ lines of duplicated YAML.
More importantly, your documentation will never drift from your validation rules again.
That alone makes it worth the 10-minute migration.