Node.js with TSOA
In preparation for the fourth Node.js testing tips article on advanced interface testing, I'll first lay the groundwork: I'll show you how to set up TSOA and integrate it with Express. You'll find the corresponding configuration, based on my best practices, in the article. Let's get started!
In this article, I explain how to set up TSOA in Node.js. Using this package, we generate both the swagger.json file and the route definition.
- The autogenerated route definition
routes.tscontains all the details that Express needs for the interface definition. We no longer need to manually include individual routes → a small automation that proactively prevents errors. - The JSON file is an Open API version 3-compliant interface definition that we can use to, for example, store documentation under the
/docsroute. This is particularly useful if we want to make it easier for other developers to work with our product within a SaaS service. At the same time, the documentation is valuable within our team: frontend and app developers can work with it without needing to look at the backend code.
TSOA Installation
Once we understand what we need this for, we'll start by installing the necessary NPM package:
npm install tsoa
Next, I recommend making some adjustments to the package.json file in the scripts section. First, add a command to manually trigger the generation of the route definition and the JSON file. We'll execute this command whenever we modify the routes in the code to make them available for local development. Secondly, you'll need to add a command to generate the route definition as a step before starting the build process. This ensures that the build pipeline always uses the most up-to-date definition.
{
"scripts": {
"build": "rimraf ./dist && npm run tsoa:gen && pkgroll",
"tsoa:gen": "tsoa spec-and-routes"
}
}
TSOA Configuration
To ensure TSOA knows what to do, we configure the corresponding settings in the tsoa.json file. I recommend the following:
noImplicitAdditionalPropertiesdefines how the interface should behave when unexpected information is transmitted. I prefer an early error message and configurethrow-on-extras.controllerPathGlobsdefines the TypeScript files that TSOA searches for the route definition to generate the automatic route definition. I've found the naming convention with the "Controller" suffix effective.specdefines where the Open API-compliant interface documentation is stored and which version it is compatible with.routesspecifies where the automatically generated route definition is stored so that we can integrate it into Express from there. If we want to implement authentication with TSOA, we can include a suitable module. I will discuss this in a later article.
compilerOptionsallows you to define paths, just like the ones I use for absolute imports in TsConfig. The same definition should be entered here.
My configuration is defined as follows:
{
"entryFile": "src/index.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"controllerPathGlobs": [
"src/**/*Controller.ts"
],
"spec": {
"outputDirectory": "dist",
"specVersion": 3
},
"routes": {
"routesDir": "src",
"authenticationModule": "./src/services/AuthMiddleware.ts"
},
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
}
}
Integrating TSOA into Express
TSOA offers a convenient export function for integration into Express. This only requires the following lines of code.
import { RegisterRoutes } from '@/routes'
// ...
const app = express()
RegisterRoutes(app)
// ....
Developing Routes with TSOA
In the TSOA configuration, we specified that Controller files would be scanned. I've made it a habit to create a folder named after each endpoint, containing the corresponding controller file. For the endpoint /users, there's /src/users with the file UserController.ts. The controller file is as follows:
import { Controller } from '@tsoa/runtime'
import { Get, Request, Route, Tags } from 'tsoa'
import UserRepository from '@/users/UserRepository'
type UserGetResponse = {
firstName: string
lastName: string
}
Route('users')
@Tags('Users')
export class UserController extends Controller {
@Get()
public async getAll(@Request() req: Request): Promise<UserGetResponse[]> {
return UserRepository.getAll()
}
// ... other routes
}
Example /src/users/UserController.ts file
Conclusion
TSOA is successfully set up. The route and Open API definitions are generated. Everything is now ready to integrate important TSOA features:
- Request body validation
- Header data definition
- Role-based authentication
I will continue with these points in the following article. Until then, have fun refining your Express app.
Update 13.12.2025 11:30 AM
To get TSOA working, a configuration still needs to be made in tsconfig.js, which I haven't mentioned:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true // is not required unless you use custom middleware
}
}