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

106 views Asked by At

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!

1

There are 1 answers

1
Jeremy Fiel On BEST ANSWER

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