GuidesOpenAPI Documentation

Migration Guide

This guide covers migrating from JSDoc-based OpenAPI documentation (v1.3 and earlier) to the declarative openapi config approach introduced in v1.4.

Why Migrate

JSDoc ApproachDeclarative Approach
Manual JSDoc commentsType-safe config objects
String-based referencesDirect schema references
No validation integrationReuses validation schemas
Error-proneCompile-time checking
Separate from route logicCo-located with route config

Migration Steps

1. Update Configuration

arkos.config.ts
import { defineConfig } from "arkos/config";

export default defineConfig({
  swagger: {
    endpoint: "/api/docs",
    options: {
      definition: {
        openapi: "3.1.0",
        info: {
          title: "My API",
          version: "1.0.0",
        },
      },
    },
  },
});
arkos.config.ts
import { ArkosConfig } from "arkos";

const arkosConfig: ArkosConfig = {
  swagger: {
    endpoint: "/api/docs",
    options: {
      definition: {
        openapi: "3.1.0",
        info: {
          title: "My API",
          version: "1.0.0",
        },
      },
    },
  },
};

export default arkosConfig;
src/app.ts
import arkos from "arkos";

arkos.init({
  swagger: {
    endpoint: "/api/docs",
    options: {
      definition: {
        openapi: "3.1.0",
        info: {
          title: "My API",
          version: "1.0.0",
        },
      },
    },
  },
});

2. Migrate Custom Routes

Before (JSDoc):

src/routers/user.router.ts
import { Router } from "express";

const router = Router();

/**
 * @swagger
 * /api/users/{id}:
 *   get:
 *     summary: Get user by ID
 *     tags: [Users]
 *     parameters:
 *       - in: path
 *         name: id
 *         required: true
 *         schema:
 *           type: string
 *     responses:
 *       200:
 *         description: User found
 *         content:
 *           application/json:
 *             schema:
 *               $ref: '#/components/schemas/User'
 *       404:
 *         description: User not found
 */
router.get("/api/users/:id", userController.getUser);

export default router;

After (ArkosRouter):

src/modules/user/user.router.ts
import { ArkosRouter } from "arkos";
import z from "zod";
import userController from "./user.controller";

const router = ArkosRouter();

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
});

const ErrorSchema = z.object({
  message: z.string(),
});

router.get(
  {
    path: "/api/users/:id",
    validation: {
      params: z.object({ id: z.string().uuid() }),
    },
    experimental: {
      openapi: {
        summary: "Get user by ID",
        tags: ["Users"],
        responses: {
          200: UserSchema,
          404: {
            content: ErrorSchema,
            description: "User not found",
          },
        },
      },
    },
  },
  userController.getUser
);

export default router;

3. Migrate Built-in Route Documentation

Before (JSDoc):

src/modules/user/user.router.ts
import { Router } from "express";
import { RouteHook } from "arkos";

export const config: RouteHook = {
  // configuration
};

const router = Router();

/**
 * @swagger
 * /api/users:
 *   get:
 *     summary: List users
 *     tags: [Users]
 *     responses:
 *       200:
 *         description: Users retrieved
 *         content:
 *           application/json:
 *             schema:
 *               type: array
 *               items:
 *                 $ref: '#/components/schemas/User'
 */
router.get("/api/users", userController.list);

export default router;

After (RouteHook):

src/modules/user/user.router.ts
import { ArkosRouter, RouteHook } from "arkos";
import z from "zod";

export const hook: RouteHook = {
  findMany: {
    experimental: {
      openapi: {
        summary: "List users",
        tags: ["Users"],
        responses: {
          200: z.array(UserSchema),
        },
      },
    },
  },
};

const router = ArkosRouter();

export default router;

4. Migrate Authentication Endpoints

Before (JSDoc):

/**
 * @swagger
 * /api/auth/login:
 *   post:
 *     summary: User login
 *     tags: [Authentication]
 *     requestBody:
 *       required: true
 *       content:
 *         application/json:
 *           schema:
 *             type: object
 *             properties:
 *               email:
 *                 type: string
 *               password:
 *                 type: string
 *     responses:
 *       200:
 *         description: Login successful
 *         content:
 *           application/json:
 *             schema:
 *               type: object
 *               properties:
 *                 token:
 *                   type: string
 *       401:
 *         description: Invalid credentials
 */

After (RouterConfig):

src/modules/auth/auth.router.ts
import { ArkosRouter, RouterConfig } from "arkos";
import z from "zod";

export const config: RouterConfig<"auth"> = {
  login: {
    validation: {
      body: z.object({
        email: z.string().email(),
        password: z.string().min(6),
      }),
    },
    experimental: {
      openapi: {
        summary: "User login",
        tags: ["Authentication"],
        responses: {
          200: {
            content: z.object({ token: z.string() }),
            description: "Login successful",
          },
          401: {
            content: z.object({ message: z.string() }),
            description: "Invalid credentials",
          },
        },
      },
    },
  },
};

const router = ArkosRouter();

export default router;

Key Differences

FeatureJSDocDeclarative
Schema definition$ref or manual propertiesDirect Zod/class-validator schemas
Parameter docsManual parameters arrayAuto from validation.query/params
Request bodyManual requestBodyAuto from validation.body
ResponsesManual schema referencesDirect schema objects
SecurityManual security arrayAuto from authentication config
Type safetyNoneFull TypeScript inference

Breaking Changes

  1. Schemas must be defined inline or imported — No more $ref strings
  2. Route config replaces JSDoc — All documentation moves into experimental.openapi
  3. ArkosRouter required — Custom routes must use ArkosRouter instead of Express Router
  4. Validation schemas drive input docs — Request body, query, and params are documented from validation config

Rollback

If you need to revert, keep both approaches temporarily:

// Can co-exist during migration
router.get(
  {
    path: "/api/users/:id",
    experimental: {
      openapi: { /* new way */ },
    },
  },
  userController.getUser
);

// Old JSDoc still works alongside
/**
 * @swagger
 * /api/users:
 *   get:
 *     summary: List users
 */
router.get("/api/users", userController.list);

Once all routes are migrated, remove the JSDoc comments and update your config.