Logo
Published on

How To Create API Documentation (FAST): Swagger With TypeScript

Authors
  • Name
    Twitter

In the world of coding, the last step of developing a new API is the preparation of a document. Things are getting ugly in this step due to its nature. We could be so lazy to document what we know but it is crucial thing to do. But is there any easy way to do it? Well, this is why you are here and I am here to show you.

What is Swagger?

Swagger is an open-source platform to describe the structure of your APIs so the machines can read them. Swagger automatically analyzes your API and creates documentation. Isn’t it great? So how do we integrate it with my project written in TypeScript 🤔.

Let’s begin with creating a new project in IntelliJ.

After creating empty JavaScript project we can delete hello.js file or remove the Add sample code click above. Now we need some external libraries of course. Here is the NPM command to install everything!

npm install ts-node swagger-autogen swagger-ui-express express @types/express @types/swagger-ui-express

After installing packages, our final folder hierarch will look like this:

Let’s create theese files one by one. First we need to create tsconfig.json file under SwaggerDemo. And our package.json should look like this.

package.json:

{
  "name": "swaggerdemo",
  "version": "1.0.0",
  "dependencies": {
    "@types/express": "^4.17.17",
    "@types/swagger-ui-express": "^4.1.3",
    "express": "^4.18.2",
    "swagger-autogen": "^2.23.5",
    "swagger-ui-express": "^5.0.0",
    "ts-node": "^10.9.1"
  },
  "main": "dist/app.js",
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node dist/app.js",
    "build": "tsc",
    "prepublish": "npm run build"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}

tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "module": "commonjs",
    "target": "es2015",
    "outDir": "./dist"
  },
  "include": [
    "src/swagger-output.json",
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ],
  "lib": [
    "es2015"
  ],
  "typeRoots": [
    "node_modules/@types"
  ],
  "types": [
    "node",
    "express"
  ]
}

build script in package.json creates dist folder for us. This folder contains js files which is codes converted from ts files.

Now, we need to code our routes.

employee-route.ts:

import {Router, Request} from 'express';

export const employeeRouter = Router();

const employees = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
    { id: 3, name: 'John Smith' },
];

interface UpdateEmployeeRequest extends Request {
    id: number;
    name: string;
}

employeeRouter.get('/getEmployees', (req, res) => {
    res.status(200).json(employees);
});

employeeRouter.get('/getEmployee/:id', (req, res) => {
    const employee = employees.find(e => e.id === parseInt(req.params.id));
    if (!employee) res.status(404).json({ message: 'Employee not found' });
    res.status(200).json(employee);
});

employeeRouter.patch('/updateEmployee', (req: UpdateEmployeeRequest, res) => {
   const employee = employees.find(e => e.id === req.body.id);
    if (!employee) res.status(404).json({ message: 'Employee not found' });

    // if employee is found, update the employee name
    employee.name = req.body.name;
    res.status(200).json(employee);
});

index.ts:

import express from 'express';

import { employeeRouter } from './employee-route';

export const routes = express.Router();

routes.use('/api/employee', employeeRouter);

If import *** from *** gives an error, there could be an error in your tsconfig.json file. It is important to give “esModuleInterop” field true and if you already defined this, you can restart your IDE.

Next, we define swagger.ts file.

swagger.ts:

import swaggerAutogen from 'swagger-autogen';

const doc = {
    info: {
        version: 'v1.0.0',
        title: 'Swagger Demo Project',
        description: 'Implementation of Swagger with TypeScript'
    },
    servers: [
        {
            url: 'http://localhost:8080',
            description: ''
        },
    ],
    components: {
        securitySchemes: {
            bearerAuth: {
                type: 'http',
                scheme: 'bearer',
            }
        }
    }
};

const outputFile = './swagger_output.json';
const endpointsFiles = ['./src/routes/index.ts'];

swaggerAutogen({openapi: '3.0.0'})(outputFile, endpointsFiles, doc);

We are using OpenApi 3.0.0 version because its more recent version :)

Final touch with app.ts file.

app.ts:

import express from "express";
import * as bodyParser from "body-parser";
import {routes} from "./routes";
import swaggerUi from "swagger-ui-express";
import swaggerOutput from "./swagger_output.json";

const app = express();

app.use(bodyParser.json());

app.use('/', routes);

app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerOutput));

app.listen(8080, () => {
    console.log('Server started at port 8080');
});

Now before starting our app, we need create and fill swagger_output.json file. This process will be done automatically by swagger.ts file.

ts-node src/swagger.ts

Run this command and you will see the magic.

Finally we can run our app and see the results.

node dist/app.js

Look at this beauty. All we did was giving the meta information about our project and routes file path. But sometimes, it can not find request bodies and shows null. To give request bodies explicitly, we can define schema in our swagger.ts file.

{  
    info: {  
        version: 'v1.0.0',  
        title: 'Swagger Demo Project',  
        description: 'Implementation of Swagger with TypeScript'  
    },  
    servers: [  
        {  
            url: 'http://localhost:8080',  
            description: ''  
        },  
    ],  
    components: {  
        securitySchemes: {  
            bearerAuth: {  
                type: 'http',  
                scheme: 'bearer',  
            }  
        },  
        schemas: {  
            UpdateEmployeeRequest: {  
                type: 'object',  
                properties: {  
                    id: 0,  
                    name: 'string'  
                }  
            }  
        }  
    }  
}

And in code,

employeeRouter.patch('/updateEmployee', (req: UpdateEmployeeRequest, res) => {  
    /**  
    #swagger.requestBody = {  
        required: true,  
        schema: { $ref: "#/components/schemas/UpdateEmployeeRequest" }  
    }  
     */  
      
   const employee = employees.find(e => e.id === req.body.id);  
    if (!employee) res.status(404).json({ message: 'Employee not found' });  
  
    // if employee is found, update the employee name  
    employee.name = req.body.name;  
    res.status(200).json(employee);  
});

This is how we can explicitly define our request bodies if swagger autogen can not find the request body type.

That’s it! It is that easy to do it on your own. You can find more information about swagger autogen here and download the code here.