Docus Logo
Charcole Swagger

Adding Swagger to Existing Charcole Projects

How to add auto-generated Swagger documentation to projects created with Charcole v2.1 or earlier.

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 NameStatusUse Case
Success200Successful GET/POST/PUT
ValidationError400Invalid request body
Unauthorized401Missing/invalid token
Forbidden403Insufficient permissions
NotFound404Resource not found
InternalError500Server 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 schemas object
  • 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?

  1. Check spelling - schema names must match exactly
  2. Verify imports - make sure schemas are actually imported
  3. 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:

  1. Start server: npm run dev
  2. Open Swagger UI: http://localhost:3000/api-docs
  3. Check schemas section: All registered schemas should appear
  4. Check each endpoint: Verify request/response schemas display correctly
  5. Try "Try it out": Test endpoints directly from Swagger UI
  6. 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.