asteasolutions / zod-to-openapi

A library that generates OpenAPI (Swagger) docs from Zod schemas
MIT License
788 stars 52 forks source link

Best practices for project structure in order to build the document #195

Closed ootkin closed 5 months ago

ootkin commented 7 months ago

Hi All,

after spending like 2 days searching for a good api spec generator, I finally end here.

The problem is that I cannot find a way to structure my project for an easy maintenace.

My structure is like this:

├── document
│   ├── document.controller.ts
│   ├── document.dto.ts
│   └── document.service.ts
├── index.ts
├── exports
│   ├── export.controller.ts
│   ├── export.dto.ts
│   └── export.service.ts
├── openapi.ts

Inside the *.dto.ts files I specify all of my zod schema and in openapi.ts I've got:

import {
  extendZodWithOpenApi,
  OpenApiGeneratorV3,
  OpenAPIRegistry,
} from "@asteasolutions/zod-to-openapi";
import { z } from "zod";
import { OpenAPIObjectConfig } from "@asteasolutions/zod-to-openapi/dist/v3.0/openapi-generator";
import { writeFileSync } from "fs";
import yaml from 'yaml';

extendZodWithOpenApi(z);
export const registry = new OpenAPIRegistry();

/**
 * Need to register all of my schemas
 */

export function generateOpenAPI() {
  const config: OpenAPIObjectConfig = {
    openapi: "3.0.0",
    info: {
      title: "Test",
      version: "0.0.0",
    },
  };
  return new OpenApiGeneratorV3(registry.definitions).generateDocument(config);
}

writeFileSync("spec.yaml", yaml.stringify(generateOpenAPI()), {
  encoding: "utf-8",
});

How can I do that?

The goal is to call this script in a CI pipeline

AGalabov commented 6 months ago

@ootkin thank you for using our library and sorry for the delayed response. December was very busy.

I can share some tips on what we've done in our project as an executable script (removed quite a lot of code for simplicity of what should be achived):

#!/usr/bin/env node

/* some other imports*/

// Import the generator function
const { generateOpenAPI } = require(generatorPath);

// Needed so that the route endpoints are "build" and registered into the OpenAPI generator
require(`${__dirname}/../path/to/entry/point/of/the/project/index.js`);

const defaultOutputFile = path.join(__dirname, '..', 'swagger.json');

const document = generateOpenAPI();

// Write data into file
const { components, ...rest } = document;
const jsonData = JSON.stringify({ ...rest, components });

const buildOutputFile = path.join(__dirname, '..', 'build', 'swagger.json');
shell.ShellString(jsonData).to(buildOutputFile);

the key part here is the fact that you've imported the entry point file to load up all definitions (through the exported registry) and only called the generateOpenAPI function after the definitions are all loaded.

So basically following your example you would need to keep registry and generateOpenAPI in that file but move the writeFileSync into a separate file for the CI part that is only called with the present defnitions.

I hope that helps. Let me know if you need more info or face other problems