I’ve been building APIs for years, and I’ve seen what happens when documentation is an afterthought. Teams waste hours explaining endpoints, clients struggle with integration, and developers constantly ask, “What does this endpoint return again?” This week, I spent three hours debugging an issue that would have taken three minutes with proper docs. That’s why I’m writing this. Good documentation isn’t just nice to have; it’s a critical part of your application’s infrastructure. Let’s build it right, together. If you find this guide helpful, please like, share, and comment with your own experiences at the end.
We’ll combine two powerful tools: TypeDoc for our internal TypeScript types and Swagger for our external REST API. This gives us complete coverage, from the code our team writes to the endpoints our clients call.
First, we need to set up our project. I prefer starting with a clean AdonisJS TypeScript installation.
npm init adonis-ts-app@latest api-docs-project
cd api-docs-project
npm install --save-dev typedoc swagger-jsdoc
npm install @adonisjs/lucid @adonisjs/auth
Our folder structure will keep things organized. I like having separate directories for generated documentation.
api-docs-project/
├── app/
│ ├── Controllers/
│ ├── Models/
│ └── Validators/
├── config/
│ ├── swagger.ts
│ └── typedoc.ts
├── docs/
│ ├── api/
│ └── types/
└── start/
Now, let’s configure TypeDoc. This tool reads our TypeScript code and creates beautiful documentation for our types, interfaces, and classes. It’s like having an automatic technical writer for your codebase.
// typedoc.json
{
"entryPoints": ["app/"],
"out": "docs/types",
"name": "Our API Internal Types",
"includeVersion": true,
"exclude": ["**/node_modules/**", "**/*.test.ts"],
"theme": "default",
"readme": "none"
}
But what about our actual API endpoints? That’s where Swagger comes in. Swagger creates interactive documentation that lets users try API calls right from their browser. Have you ever wished you could test an endpoint without writing any code?
We need to configure Swagger to work with AdonisJS. Create a new config file.
// config/swagger.ts
export default {
title: 'Our Production API',
version: '1.0.0',
description: 'Complete API documentation with examples',
route: '/api-docs',
uiConfig: {
docExpansion: 'list',
deepLinking: true
},
spec: {
info: {
title: 'Our Production API',
version: '1.0.0',
},
servers: [
{
url: 'http://localhost:3333',
description: 'Development server'
}
]
}
}
The real magic happens when we document our models. TypeDoc comments become living documentation that stays in sync with our code. Let me show you what I mean.
// app/Models/User.ts
/**
* Represents a user in our system
*
* Users can authenticate, own products, and have specific roles.
* Passwords are hashed using Argon2 before storage.
*
* @example
* // Creating a new user
* const user = await User.create({
* email: '[email protected]',
* password: 'securePassword123'
* })
*/
export default class User extends BaseModel {
/**
* The user's unique identifier
* @type {number}
*/
@column({ isPrimary: true })
public id: number
/**
* Email address for authentication
* Must be unique across all users
* @type {string}
*/
@column()
public email: string
}
See how those comments work? They’re not just for developers reading the code. TypeDoc turns them into formal documentation. But here’s a question: what happens when your API changes? How do you keep documentation current?
That’s where automation comes in. We can add scripts to our package.json to generate docs automatically.
// package.json
{
"scripts": {
"docs:types": "typedoc",
"docs:api": "node -r @adonisjs/require-ts/build/register generate-swagger.js",
"docs": "npm run docs:types && npm run docs:api"
}
}
Now for the Swagger integration. We need to document our actual endpoints. I create a middleware that reads JSDoc comments and builds Swagger specifications.
// start/swagger.ts
import swaggerJSDoc from 'swagger-jsdoc'
import swaggerUi from 'swagger-ui-express'
import Application from '@ioc:Adonis/Core/Application'
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Our API',
version: '1.0.0',
},
},
apis: [Application.appRoot + '/app/Controllers/**/*.ts'],
}
const swaggerSpec = swaggerJSDoc(options)
// Register with Adonis server
Server.middleware.registerNamed({
swagger: () => import('App/Middleware/SwaggerMiddleware')
})
Let’s look at a controller with proper Swagger documentation. Notice how the comments follow a specific format that Swagger understands.
// app/Controllers/Http/UsersController.ts
export default class UsersController {
/**
* @swagger
* /api/users:
* get:
* summary: Get all users
* tags: [Users]
* responses:
* 200:
* description: List of users
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: '#/components/schemas/User'
*/
public async index({ response }: HttpContextContract) {
const users = await User.all()
return response.json(users)
}
/**
* @swagger
* /api/users/{id}:
* get:
* summary: Get a specific user
* tags: [Users]
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* responses:
* 200:
* description: User found
* 404:
* description: User not found
*/
public async show({ params, response }: HttpContextContract) {
const user = await User.find(params.id)
if (!user) {
return response.status(404).json({ error: 'User not found' })
}
return response.json(user)
}
}
But what about authentication? Many APIs require tokens or keys. Swagger can handle this too. Did you know users can test authenticated endpoints right from the documentation?
We need to define security schemes in our Swagger configuration.
// config/swagger.ts - updated
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT'
}
},
security: [{
bearerAuth: []
}]
Then we apply it to our endpoints:
/**
* @swagger
* /api/protected:
* get:
* summary: Get protected data
* security:
* - bearerAuth: []
* responses:
* 200:
* description: Protected data
*/
Now comes an interesting challenge: validation. AdonisJS has great validators, but how do we document what data an endpoint expects? We can link our validators to our Swagger documentation.
// app/Validators/CreateUserValidator.ts
/**
* @swagger
* components:
* schemas:
* CreateUserRequest:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* format: email
* password:
* type: string
* minLength: 8
*/
export default class CreateUserValidator {
constructor(protected ctx: HttpContextContract) {}
public schema = schema.create({
email: schema.string({ trim: true }, [
rules.email(),
rules.unique({ table: 'users', column: 'email' })
]),
password: schema.string({}, [rules.minLength(8)])
})
}
I often get asked about deployment. Where should documentation live? I recommend generating it during your build process and serving it from your application. This way, it’s always in sync with the deployed version.
Here’s a production setup:
// start/routes.ts
if (process.env.NODE_ENV !== 'production') {
Route.get('/docs/api', async ({ response }) => {
return response.download('docs/api/swagger.json')
})
Route.get('/docs/types', async ({ response }) => {
return response.download('docs/types/index.html')
})
}
For production, you might want to protect your documentation or serve it from a CDN. The key is making it accessible to your team and clients while keeping it secure.
What happens when you have multiple API versions? This is a common problem as APIs evolve. I handle this by versioning my documentation.
docs/
├── v1/
│ ├── api/
│ └── types/
└── v2/
├── api/
└── types/
Each version gets its own generated documentation. This prevents confusion and helps clients migrate between versions.
Let me share a personal tip: I always include examples in my documentation. Real, working examples save everyone time. Here’s how I add them to Swagger:
/**
* @swagger
* /api/users:
* post:
* summary: Create a new user
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/CreateUserRequest'
* examples:
* normalUser:
* summary: Normal user registration
* value:
* email: "user@example.com"
* password: "SecurePass123"
* adminUser:
* summary: Admin user creation
* value:
* email: "admin@example.com"
* password: "AdminPass123"
* role: "admin"
*/
The final piece is automation. I set up Git hooks to ensure documentation stays current. A pre-commit hook can check if documentation needs updating.
#!/bin/bash
# .git/hooks/pre-commit
npm run docs
git add docs/
This ensures that whenever code changes, documentation updates too. No more forgetting to update docs after changing an endpoint.
Remember, documentation is a living part of your application. It needs care and attention, just like your tests and code. Good documentation reduces support requests, speeds up integration, and makes your API more professional.
I’ve seen teams transform their workflow with proper documentation. New developers get up to speed faster. Clients integrate more quickly. Bugs get caught earlier. It’s worth the investment.
Start small if you need to. Document one endpoint perfectly. See how it feels. Then expand from there. Your future self will thank you, and so will everyone who uses your API.
What documentation challenges have you faced? Share your stories in the comments below. If this guide helped you understand API documentation better, please like and share it with your team. Let’s build better documented APIs together.
As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!
101 Books
101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.
Check out our book Golang Clean Code available on Amazon.
Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!
📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!
Our Creations
Be sure to check out our creations:
Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools
We are on Medium
Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva