Search code examples
node.jsexpressswaggerswagger-uiswagger-jsdocs

Why do I get the "No operations defined in spec!" message when accessing Swagger documentation for my app?


so I'm trying to set up Swagger for my NodeJS app but I'm struggling with a problem for a while now. I will try to explain as detailed as possible.

So I have swagger.util.js file:

import swaggerJSDoc from "swagger-jsdoc";

const options = {
    failOnErrors: true,
    definition: {
        openapi: "3.0.0",
        info: {
            title: "API",
            version: "1.0.0",
            description: "Endpoints"
        },
        servers: [
            {
                url: "http://localhost:80",
                description: "HTTP"
            },
            {
                url: "http://localhost:443",
                description: "HTTPS"
            }
        ],
    },
    apis: ["../routes/*.js"]
};

const specs = swaggerJSDoc(options);

export default specs;

This user.route.js file:

import express from "express";
import trimRequest from "trim-request";

import { register } from "../controllers/user.controller.js";

const router = express.Router();

/**
 * @swagger
 * components:
 *   schemas:
 *     UserAccount:
 *       type: object
 *       required:
 *         - username
 *         - email
 *         - password
 *       properties:
 *         username:
 *           type: string
 *         email:
 *           type: string
 *         password:
 *           type: string
 *       example:
 *         username: Test
 *         email: [email protected]
 *         password: TestTest1!
 */
router.route("/register").post(trimRequest.all, register);

export default router;

And this app.js file that is used in index.js:

import compression from "compression";
import cookieParser from "cookie-parser";
import cors from "cors";
import dotenv from "dotenv";
import express from "express";
import mongoSanitize from "express-mongo-sanitize";
import helmet from "helmet";
import createHttpError from "http-errors";
import morgan from "morgan";
import swaggerUI from "swagger-ui-express";

import routes from "./routes/index.route.js";
import specs from "./utils/swagger.utils.js";

dotenv.config();

const app = express();

app.use(compression());
app.use(cookieParser());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({
    extended: true
}));
app.use(helmet());
app.use(mongoSanitize());
if (process.env.ENV !== "prd") {
    app.use(morgan("dev"));
}
app.use("/docs", swaggerUI.serve, swaggerUI.setup(specs));
console.log(specs);

// Remove this later
app.get("/", (_req, res) => {
    res.send("Hello");
});

app.use("/api/v1", routes);

app.use(async (_req, __res, next) => {
    next(createHttpError.NotFound("This route does not exist"));
});

app.use(async (err, _req, res,_next) => {
    res.status(err.status || 500);
    res.send({
        error: {
            status: err.status || 500,
            message: err.message,
        },
    });
});

export default app;

The folder structure is the following:

-src
 -controllers
  -user.controller.js
 -routes
  -index.route.js
  -user.route.js
 -utils
  -swagger.utils.js
 -app.js
 -index.js

The problem is that console.log(specs); from app.js returns a json that has the components field empty (empty object) for some reason. But it should return something since I have that JSDoc comment in the user.route.js right? console
And as I said, the doc is empty too:
doc

Do you know which might be the cause/problem? Or how can I fix this? Thank you!


Solution

  • I believe your issue is where you run the command from. If you're running the command from the root of the project, your relative path is incorrect.

    const options = {
        definition: {
    
            openapi: '3.0.3',
            info: {
                title: 'test',
                version: '1.0.0'
            },
            servers: [
                { url: 'https://www.example.com/v1' }
            ]
        },
        apis: [
            'src/routes/user.route.js'  // <<< this can't use a relative ref. 
        ]
    }
    

    Here's a simple repo

    📦src
     ┣ 📂routes
     ┃ ┗ 📜user.route.js
     ┣ 📂utils
     ┃ ┗ 📜openapi.util.js
     ┗ 📜index.js
    
    // index.js
    import express from 'express'
    import swaggerUI from 'swagger-ui-express'
    import OASdescription from './utils/openapi.util.js'
    
    const app = express()
    app.use(express.json())
    const PORT = process.env.PORT || 3000
    
    app.use('/api/docs', swaggerUI.serve, swaggerUI.setup(OASdescription))
    
    app.listen(PORT, () => console.log(`listening on port ${PORT}`))
    
    // openapi.util.js
    import swaggerJsdoc from 'swagger-jsdoc'
    
    const options = {
        definition: {
    
            openapi: '3.0.3',
            info: {
                title: 'test',
                version: '1.0.0'
            },
            servers: [
                { url: 'https://www.example.com/v1' }
            ]
        },
        apis: [
            'src/routes/user.route.js'
        ]
    }
    
    const OASDescription = swaggerJsdoc(options)
    
    export default OASDescription
    
    // user.route.js
    import { Router } from 'express'
    
    const router = Router()
    
    /**
    * @openapi
    * '/user':
    *   get:
    *     description: my hosted api docs
    *     summary: api docs
    *     responses:
    *       '200':
    *         description: OK
    *         content:
    *           'application/json':
    *             schema:
    *               type: object
    *               properties:
    *                 thing:
    *                   $ref: '#/components/schemas/UserAccount'
    * components:
    *   schemas:
    *     UserAccount:
    *       type: object
    *       required:
    *         - username
    *       properties:
    *         username:
    *           type: string
    */
    router.get('/user', (req, res) => {
    
        res.status('200').json({ "username": "test" })
    })
    
    export default router
    

    swagger