I'm using Swagger, and everything was working perfectly on my localhost. However, after deploying my project to Vercel, Swagger no longer works at all. I encountered the following error:
After reading a post on Stack Overflow (unfortunately, I can't find the link anymore), I tried switching my "swagger-ui-express" version to 4.6.2. This partially solved the issue: now, I can access the Swagger UI, but no operations are found in specs.
No operations defined in spec!
Here are more details about my configuration:
import express, { Express, Request, Response } from 'express';
import 'dotenv/config';
import { setupCors } from './utils/cors';
import { setupSwagger } from './utils/swagger';
import { logger, setupLogger } from './utils/logger';
import { setupRoutes } from './utils/routes';
const app: Express = express();
// JSON Parser
app.use(express.json());
// CORS
setupCors(app);
// Logger
setupLogger(app);
// Swagger
setupSwagger(app);
// Routes
setupRoutes(app);
// Start server
const port = process.env.PORT || 3000;
app.listen(port, () => {
logger.info(`Server running : http://localhost:${port}`);
logger.info(`Swagger running : http://localhost:${port}/api-docs`);
logger.info('Server started !');
});
import swaggerJSDoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import { Application } from 'express';
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'App',
version: '0.0.0',
description: 'An app',
},
components: {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
},
},
},
apis: ['./src/routes/*.ts'],
};
const CSS_URL =
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.1.0/swagger-ui.min.css';
const swaggerSpec = swaggerJSDoc(options);
export const setupSwagger = (app: App`your text`lication) => {
app.use(
'/api-docs',`your text`
swaggerUi.serve,
swaggerUi.setup(swaggerSpec, {
customCss:
'.swagger-ui .opblock .opblock-summary-path-description-wrapper { align-items: center; display: flex; flex-wrap: wrap; gap: 0 10px; padding: 0 10px; width: 100%; }',
customCssUrl: CSS_URL,
}),
);
};
import express from 'express';
import { signUp, signIn } from '../controllers/auth';
import {
validateEmail,
validateName,
validatePassword,
} from '../validator/signupSchema';
import { fieldsValidation } from '../middlewares/fieldsValidation';
import { authorizeAccess } from '../middlewares/routesAuthorization';
export const authRouter = express.Router();
/**
* @swagger
* /auth/sign-up:
* post:
* summary: Sign up a new user
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* password:
* type: string
* responses:
* 200:
* description: User signed up successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: User signed up successfully
* data:
* type: object
* properties:
* id:
* type: integer
* example: 5
* name:
* type: string
* example: John Doe
* email:
* type: string
* example: johndoe@example.com
* password_hash:
* type: string
* example: "$2b$10$tnHGADEJL0QDYDdkq3YeQeGVvirjwKfaWIGXtjYJiFCBniyxYpgRe"
* role:
* type: string
* example: customer
* created_at:
* type: string
* example: "2025-02-23T02:05:22.023Z"
* 400:
* description: Invalid input (e.g., password doesn't meet security requirements)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Password must contain at least one uppercase letter.
* 409:
* description: User already exists (e.g., email already registered)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: User already exists
* 500:
* description: Internal server error (e.g., unexpected failure on the server side)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Internal server error
*/
authRouter.post(
'/sign-up',
[validateName, validateEmail, validatePassword],
fieldsValidation,
signUp,
);
/**
* @swagger
* /auth/sign-in:
* post:
* summary: Sign in a user
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* password:
* type: string
* responses:
* 200:
* description: User signed in successfully
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: User johndoe@example.com is authenticated
* data:
* type: object
* properties:
* id:
* type: integer
* example: 4
* name:
* type: string
* example: John Doe
* email:
* type: string
* example: johndoe@example.com
* password_hash:
* type: string
* example: "$2b$10$z/1y41pGVlsgxhAZL7GHEuutbo3c1NJFWDZ4TPCZjRzehQvLMVoku"
* role:
* type: string
* example: customer
* created_at:
* type: string
* example: "2025-02-23T00:44:53.309Z"
* token:
* type: string
* example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NCwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huZG9lQGV4YW1wbGUuY29tIiwicm9sZSI6ImN1c3RvbWVyIiwiaWF0IjoxNzQwMjc2NjUwLCJleHAiOjE3NDAzNjMwNTAsImF1ZCI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJ9.dte0uLjohUur7bjgtT21IzXutNx8R3miRHcLJc05-w4"
* 400:
* description: Invalid input (e.g., email doesn't meet requirements)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Bad email format.
* 401:
* description: Invalid credentials (e.g., user doesn't exist or password is incorrect)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Invalid credentials.
* 500:
* description: Internal server error (e.g., unexpected failure on the server side)
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: Internal server error
*/
authRouter.post(
'/sign-in',
[validateEmail, validatePassword],
fieldsValidation,
signIn,
);
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon src/index.ts",
"build": "tsc -p ."
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@types/bcrypt": "^5.0.2",
"bcrypt": "^5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-validator": "^7.2.1",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"pg": "^8.13.3",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^4.6.2",
"winston": "^3.17.0",
"z-schema": "^6.0.2"
},
"devDependencies": {
"@types/cors": "^2.8.17",
"@types/express": "^5.0.0",
"@types/jsonwebtoken": "^9.0.9",
"@types/morgan": "^1.9.9",
"@types/node": "^22.13.4",
"@types/pg": "^8.11.11",
"@types/swagger-jsdoc": "^6.0.4",
"@types/swagger-ui-express": "^4.1.8",
"nodemon": "^3.1.9",
"ts-node": "^10.9.2",
"typescript": "^5.7.3"
}
}
{
"version": 2,
"builds": [
{
"src": "src/index.ts",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "src/index.ts"
}
]
}
You just have to change :
apis: ['./src/routes/*.ts'],
to :
apis: ['./src/routes/*.js'],
in your "src/utils/swagger.ts" file