Last checked with Wasp 0.23, swagger-jsdoc 6, and swagger-ui-express 5.
This guide depends on external libraries or services, so it may become outdated over time. We do our best to keep it up to date, but make sure to check their documentation for any changes.Swagger UI
This guide shows you how to add Swagger UI documentation to your Wasp APIs, making it easy to explore and test your endpoints.
Setting up Swagger UI
1. Install dependencies
Install the required packages:
npm install swagger-jsdoc swagger-ui-express
npm install --save-dev @types/swagger-jsdoc @types/swagger-ui-express
2. Configure the API namespace in main.wasp
Add an API namespace for the Swagger UI endpoint:
app MyApp {
wasp: {
version: "^0.21.0"
},
title: "my-app",
}
apiNamespace swaggerUI {
middlewareConfigFn: import { swaggerMiddleware } from "@src/swagger-ui",
path: "/api-docs"
}
api getStatus {
fn: import { getStatus } from "@src/apis",
auth: false,
entities: [],
httpRoute: (GET, "/status")
}
3. Create the spec generator script
Because swagger-jsdoc scans source files at runtime and src/ is not available in the production Docker image, you need to pre-generate the spec as a TypeScript module that gets bundled with the server.
Create scripts/generate-swagger.js:
import swaggerJsdoc from "swagger-jsdoc";
import { writeFileSync } from "fs";
const spec = swaggerJsdoc({
definition: {
openapi: "3.0.0",
info: {
title: "My API",
version: "1.0.0",
description: "API documentation for my Wasp application",
contact: {
name: "API Support",
url: "https://example.com",
email: "support@example.com",
},
},
components: {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
description: "Enter your JWT token in the format: Bearer {token}",
},
},
},
security: [{ bearerAuth: [] }],
},
apis: ["./src/**/*.ts", "!./src/swaggerSpec.ts"],
});
writeFileSync(
"./src/swaggerSpec.ts",
`const swaggerSpec = ${JSON.stringify(spec, null, 2)} as const;\nexport default swaggerSpec;\n`,
);
console.log("swaggerSpec.ts generated");
Run this script whenever you add or change JSDoc annotations:
node scripts/generate-swagger.js
This generates src/swaggerSpec.ts, which gets bundled into the server and works in both dev and production.
You may want to add src/swaggerSpec.ts to your .gitignore since it's a generated file.
4. Create the Swagger middleware
Create the Swagger UI middleware that serves the pre-generated spec:
- JavaScript
- TypeScript
import * as express from "express";
import helmet from "helmet";
import swaggerUi from "swagger-ui-express";
import { env, MiddlewareConfigFn } from "wasp/server";
import baseSwaggerDoc from "./swaggerSpec";
const swaggerDoc = {
...baseSwaggerDoc,
servers: [{ url: env.WASP_SERVER_URL, description: "API server" }],
};
export const swaggerMiddleware = (middlewareConfig) => {
middlewareConfig.delete("helmet");
middlewareConfig.set(
"helmet",
helmet({
contentSecurityPolicy: false,
hsts: false,
}),
);
swaggerUi.serve.forEach((handler, i) => {
middlewareConfig.set(`swaggerServe${i}`, handler);
});
middlewareConfig.set("swaggerSetup", (req, res, next) => {
return swaggerUi.setup(swaggerDoc, {
explorer: true,
customCss: ".swagger-ui .topbar { display: none }",
swaggerOptions: {
persistAuthorization: true,
url: "/api-docs/swagger.json",
},
})(req, res, next);
});
return middlewareConfig;
};
import * as express from "express";
import helmet from "helmet";
import swaggerUi from "swagger-ui-express";
import { env, MiddlewareConfigFn } from "wasp/server";
import baseSwaggerDoc from "./swaggerSpec";
const swaggerDoc = {
...baseSwaggerDoc,
servers: [{ url: env.WASP_SERVER_URL, description: "API server" }],
};
export const swaggerMiddleware: MiddlewareConfigFn = (middlewareConfig) => {
middlewareConfig.delete("helmet");
middlewareConfig.set(
"helmet",
helmet({
contentSecurityPolicy: false,
hsts: false,
}),
);
swaggerUi.serve.forEach((handler, i) => {
middlewareConfig.set(`swaggerServe${i}`, handler);
});
middlewareConfig.set(
"swaggerSetup",
(
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
return swaggerUi.setup(swaggerDoc, {
explorer: true,
customCss: ".swagger-ui .topbar { display: none }",
swaggerOptions: {
persistAuthorization: true,
url: "/api-docs/swagger.json",
},
})(req, res, next);
},
);
return middlewareConfig;
};
5. Document your APIs
Add JSDoc comments with Swagger annotations above your API definitions:
- JavaScript
- TypeScript
import { GetStatus } from "wasp/server/api";
/**
* @swagger
* /status:
* get:
* summary: Get API status
* description: Returns the current status of the API
* tags:
* - Status
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* 401:
* description: Unauthorized
* 500:
* description: Server error
*/
export const getStatus = async (req, res) => {
return res.json({ message: "OK" });
};
import { GetStatus } from "wasp/server/api";
/**
* @swagger
* /status:
* get:
* summary: Get API status
* description: Returns the current status of the API
* tags:
* - Status
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* 401:
* description: Unauthorized
* 500:
* description: Server error
*/
export const getStatus: GetStatus = async (req, res) => {
return res.json({ message: "OK" });
};
6. Access the documentation
Start your Wasp application and navigate to http://localhost:3001/api-docs to see your API documentation.
Documenting Different Request Types
POST Request with Body
/**
* @swagger
* /users:
* post:
* summary: Create a new user
* tags:
* - Users
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* format: email
* password:
* type: string
* minLength: 8
* responses:
* 201:
* description: User created successfully
* 400:
* description: Invalid input
*/
Path Parameters
/**
* @swagger
* /users/{id}:
* get:
* summary: Get user by ID
* tags:
* - Users
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User ID
* responses:
* 200:
* description: User found
* 404:
* description: User not found
*/
Query Parameters
/**
* @swagger
* /users:
* get:
* summary: List users
* tags:
* - Users
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* default: 1
* description: Page number
* - in: query
* name: limit
* schema:
* type: integer
* default: 10
* description: Items per page
* responses:
* 200:
* description: List of users
*/
Customization
Custom Styling
You can customize the Swagger UI appearance:
- JavaScript
- TypeScript
swaggerUi.setup(swaggerDoc, {
customCss: `
.swagger-ui .topbar { display: none }
.swagger-ui .info { margin: 20px 0 }
`,
customSiteTitle: "My API Documentation",
customfavIcon: "/favicon.ico",
});
swaggerUi.setup(swaggerDoc, {
customCss: `
.swagger-ui .topbar { display: none }
.swagger-ui .info { margin: 20px 0 }
`,
customSiteTitle: "My API Documentation",
customfavIcon: "/favicon.ico",
});
Group APIs with Tags
Use tags to organize your endpoints. Add a tags array to the definition in scripts/generate-swagger.js:
- JavaScript
- TypeScript
const spec = swaggerJsdoc({
definition: {
// ... other config
tags: [
{ name: "Users", description: "User management endpoints" },
{ name: "Posts", description: "Blog post endpoints" },
{ name: "Auth", description: "Authentication endpoints" },
],
},
apis: ["./src/**/*.ts", "!./src/swaggerSpec.ts"],
});
const spec = swaggerJsdoc({
definition: {
// ... other config
tags: [
{ name: "Users", description: "User management endpoints" },
{ name: "Posts", description: "Blog post endpoints" },
{ name: "Auth", description: "Authentication endpoints" },
],
},
apis: ["./src/**/*.ts", "!./src/swaggerSpec.ts"],
});
For more options, see the swagger-jsdoc documentation and swagger-ui-express documentation.