Skip to content
Guides

How to Generate an OpenAPI Spec from FastAPI, NestJS, Express, Laravel and More

In this video

Sooner or later a tool asks for your OpenAPI spec: a docs site, an SDK generator, an AI agent platform, an n8n node builder. A lot of teams think they don't have one.

You probably do. If your API runs on FastAPI, NestJS, Laravel, ASP.NET Core, Django REST framework or Spring Boot, the framework already knows every route you've written, and it can write the spec for you. Sometimes it's already served at a URL on your dev server.

This guide shows where the spec lives in each framework, how to export it as a file, and what to fix before anyone builds on it.

Generated beats hand-written

There are two ways to have a spec. You write it by hand, as a YAML file next to your code. Or the framework reads your routes and types and writes it for you, which is called code-first.

Hand-written specs drift: someone adds an endpoint, and the YAML never hears about it. A generated spec is made from the code that runs, so it can't drift that way. Everything below is code-first.

FastAPI

FastAPI generates the spec with no setup. Run the app and open:

http://localhost:8000/openapi.json

Before you hand it to anyone, fix two things.

The operationIds. By default FastAPI joins the function name, the path and the method, so get_contact on /contacts/{contact_id} gets an ID like get_contact_contacts__contact_id__get. That string ends up as SDK method names and menu labels. Use the function name instead:

from fastapi import APIRouter, FastAPI

app = FastAPI(generate_unique_id_function=lambda route: route.name)
router = APIRouter(tags=["Contacts"])

@router.get("/contacts/{contact_id}", summary="Get a contact")
def get_contact(contact_id: str): ...

Function names must then be unique across the app.

The tags. Tags group operations into sections in docs and into resources in tools. Without them, every operation lands in one group called "default". Put a tag on each router.

To save the spec as a file, for example in CI:

python -c "import json; from app.main import app; print(json.dumps(app.openapi(), indent=2))" > openapi.json

Change app.main to wherever your app lives.

NestJS

Nest generates a spec once you add @nestjs/swagger:

import { writeFileSync } from 'node:fs';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

const config = new DocumentBuilder()
  .setTitle('Acme CRM')
  .addBearerAuth()
  .build();

const document = SwaggerModule.createDocument(app, config, {
  operationIdFactory: (controllerKey, methodKey) => methodKey,
});
writeFileSync('openapi.json', JSON.stringify(document, null, 2));
  • operationIds. The default joins the controller and method names, such as ContactsController_create. The factory above uses the method name alone; method names must then be unique across controllers.
  • Request bodies. Nest reads your DTO classes, but it can't see their fields unless you decorate each one with @ApiProperty(). The Swagger CLI plugin does it for you from your TypeScript types. Turn it on in nest-cli.json:
{
  "compilerOptions": {
    "plugins": ["@nestjs/swagger"]
  }
}
  • Tags and login. Put @ApiTags('Contacts') on each controller. If you use tokens, addBearerAuth() in the document builder, as above, adds the security scheme.

Express

Express doesn't know what your routes accept or return, so something has to describe them. Pick where that description lives:

  • zod-to-openapi (@asteasolutions/zod-to-openapi), if you already validate requests with Zod. Your validation schemas become the spec, so the two can't disagree.
  • tsoa, if you're starting fresh and don't mind controller classes. It reads your TypeScript types at build time and generates both the Express routes and the spec.
  • swagger-jsdoc, which builds the spec from comments above your routes. It works, but the comments are hand-written YAML, and they drift like any hand-written spec.

Laravel, ASP.NET Core, Django and Spring Boot

Framework Add The spec is at
Laravel Scramble (dedoc/scramble) /docs/api.json
ASP.NET Core AddOpenApi() and MapOpenApi() /openapi/v1.json
Django REST framework drf-spectacular a management command
Spring Boot springdoc-openapi /v3/api-docs and /v3/api-docs.yaml

Laravel. Scramble reads your code without annotations and produces OpenAPI 3.1. Its routes only work in the local environment by default; the viewApiDocs gate controls who else can see them. To get a file, run:

php artisan scramble:export

ASP.NET Core. OpenAPI generation is built in from .NET 9:

builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();

Django REST framework. Add drf_spectacular to INSTALLED_APPS, set it as the schema class, and write the file:

python manage.py spectacular --file schema.yml

Spring Boot. Add the springdoc-openapi starter for your Spring Boot version and fetch /v3/api-docs.yaml from the running app.

Every one of these makes the same first-draft mistakes as FastAPI and NestJS: odd operation names, missing tags, sometimes no security scheme. The checks at the end of this guide apply to all of them.

A Postman collection, or an old Swagger file

Postman. Converters such as @readme/postman-to-openapi turn a v2.0 or v2.1 collection into OpenAPI 3.0. Expect to tidy the result. A collection records example requests, so the output knows what you sent once, not every field that's allowed or which ones are required.

Swagger 2.0. swagger2openapi upgrades a Swagger 2.0 file to OpenAPI 3.0, and the result is much cleaner. Check two things afterwards:

  • servers, which replaces Swagger's host, basePath and schemes.
  • Request bodies, which move from body parameters to requestBody. Check that required bodies are still marked required.

Export it, lint it, check it

Three habits turn a generated spec into one people can rely on.

  1. Export it as a file and commit it. Then every change to your API shows up as a change to the spec in code review.
  2. Lint it in CI. Spectral and Redocly both catch missing operationIds and duplicate names for free:
npx @stoplight/spectral-cli lint openapi.json
npx @redocly/cli lint openapi.json
  1. Look at it the way a tool will. Load it into something that reads specs and read the names it shows you.

For the last step, we use our own tool, NativeShip. You upload the file or give it a URL, and before you build anything it shows what it found: on our test spec, both resources from the tags, all ten operations, and the bearer login from the security scheme. The node editor's n8n preview then shows the operation names your users will pick from, and you can rename any of them before building. If the cause is a vague summary or operationId in your spec, fix that in the code too, once, for every tool.

If you import by URL, it has to be a public HTTPS address. A localhost URL won't work, because the import runs on a server, not on your machine.

OpenAPI spec best practices covers what to put in the spec once you have one: summaries, enums, security schemes and pagination. If Make is next on your list, here's how your spec maps to a Make custom app.

Recap

  • Your framework has probably been writing your spec already. Find the route it's served at.
  • Fix the operationIds so they read as names.
  • Add tags, and a security scheme if your API needs a login.
  • For Express, describe your routes with Zod or tsoa rather than comments.
  • Convert Postman or Swagger 2.0 files, then check servers and request bodies.
  • Export the spec as a file, lint it in CI, and commit it.

Once your spec looks right, NativeShip turns it into an n8n node package you publish under your own name. If you'd rather build the node by hand, the Build your own n8n node series goes from an empty folder to a verified node.

Frequently asked questions

Run the app and open /openapi.json. To save it as a file, call app.openapi() and write the result to disk. Set generate_unique_id_function so operationIds use the function name instead of the long default.

Install @nestjs/swagger, build a document with DocumentBuilder and SwaggerModule.createDocument, and write it to a file. Turn on the Swagger CLI plugin in nest-cli.json so the fields of your DTO classes appear in the

Express doesn't know your request and response shapes, so something has to describe them. Use zod-to-openapi if you validate with Zod, tsoa if you're happy with controller classes, or swagger-jsdoc, which builds the spec from comments and drifts like a hand-written file.

For Postman, use a converter such as @readme/postman-to-openapi, then add the fields a collection doesn't record. For Swagger 2.0, use swagger2openapi, then check the servers block and the request bodies, the two parts that changed shape between versions.

Ready to launch your n8n, Make, Zapier or Activepieces integration?

Give your customers another way to use your product.

Get started