admin管理员组

文章数量:1287512

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:

Blank Page

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:

Tree

My project tree

src/index.ts :

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 !');
});

src/utils/swagger.ts

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 =
  '.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,
    }),
  );
};

src/routes/auth.ts

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: [email protected]
 *                     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 [email protected] is authenticated
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: integer
 *                       example: 4
 *                     name:
 *                       type: string
 *                       example: John Doe
 *                     email:
 *                       type: string
 *                       example: [email protected]
 *                     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,
);

package.json

{
  "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",
    "man": "^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/man": "^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"
  }
}

vercel.json

{
  "version": 2,
  "builds": [
    {
      "src": "src/index.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/index.ts"
    }
  ]
}

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:

Blank Page

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:

Tree

My project tree

src/index.ts :

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 !');
});

src/utils/swagger.ts

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/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,
    }),
  );
};

src/routes/auth.ts

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: [email protected]
 *                     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 [email protected] is authenticated
 *                 data:
 *                   type: object
 *                   properties:
 *                     id:
 *                       type: integer
 *                       example: 4
 *                     name:
 *                       type: string
 *                       example: John Doe
 *                     email:
 *                       type: string
 *                       example: [email protected]
 *                     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,
);

package.json

{
  "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",
    "man": "^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/man": "^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"
  }
}

vercel.json

{
  "version": 2,
  "builds": [
    {
      "src": "src/index.ts",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/(.*)",
      "dest": "src/index.ts"
    }
  ]
}
Share Improve this question asked Feb 23 at 19:49 exooexoo 31 bronze badge
Add a comment  | 

1 Answer 1

Reset to default 0

You just have to change :

  apis: ['./src/routes/*.ts'],

to :

  apis: ['./src/routes/*.js'],

in your "src/utils/swagger.ts" file

本文标签: SwaggerNo operations defined in specExpress Typescript on VercelStack Overflow