Skip to content

NestJS server codegen #21494

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ org.openapitools.codegen.languages.TypeScriptFetchClientCodegen
org.openapitools.codegen.languages.TypeScriptInversifyClientCodegen
org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen
org.openapitools.codegen.languages.TypeScriptNestjsClientCodegen
org.openapitools.codegen.languages.TypeScriptNestjsServerCodegen
org.openapitools.codegen.languages.TypeScriptNodeClientCodegen
org.openapitools.codegen.languages.TypeScriptReduxQueryClientCodegen
org.openapitools.codegen.languages.TypeScriptRxjsClientCodegen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# OpenApi Generator _typescript-nestjs-server_

Usage: The generated output is intended to be its own module, that can be imported into your NestJS App Module. You do not need to change generated files, just import the module and implement the API

Example usage (with the openapi sample `petstore.yaml`):

1. Invoke openapi-generator
```
openapi-generator-cli.jar generate -i petstore.yaml -g typescript-nestjs-server -o api-module/
```
2. implement the contracts from `api-module/api`

`handlers/PetService.ts`:
```typescript
import { Pet, ApiResponse } from "models";
import { Observable } from "rxjs";
import { PetApi } from "../api";
import { Inject, Injectable } from "@nestjs/common";

@Injectable()
export class PetService implements PetApi {
addPet(pet: Pet, request: Request): Pet | Promise<Pet> | Observable<Pet> {
throw new Error("Method not implemented.");
}

deletePet(petId: number, apiKey: string, request: Request): void | Promise<void> | Observable<void> {
throw new Error("Method not implemented.");
}

...
```

3. Import the API Module with a reference to your implementation
`app.module.ts`
```typescript
import { Module } from "@nestjs/common";
import { ApiModule } from "api-module/api.module";
import { PetService } from "./handlers/PetService";
import { UserService } from "./handlers/UserService";
import { StoreService } from "./handlers/StoreService";

@Module({
imports: [
ApiModule.forRoot({
petApi: PetService,
userApi: UserService,
storeApi: StoreService
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
```

You now can regenerate the API module as often as you want without overriding your implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Type } from '@nestjs/common';
{{#apiInfo}}
{{#apis}}
{{#operations}}
import { {{classname}} } from '{{apiPackage}}';
{{/operations}}
{{/apis}}

export type ApiImplementations = {
{{#apis}}
{{#operations}}
{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}: Type<{{classname}}>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is the implementer supposed to use this? can you clarify that in the PR description?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the description as well as the committed README

{{/operations}}
{{/apis}}
};
{{/apiInfo}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { DynamicModule, Module, Provider } from '@nestjs/common';
import { ApiImplementations } from './api-implementations'
{{#apiInfo}}
{{#apis}}
import { {{classname}} } from './{{apiPackage}}';
import { {{classname}}Controller } from './controllers';
{{/apis}}
{{/apiInfo}}

@Module({})
export class ApiModule {
static forRoot(apiImplementations: ApiImplementations): DynamicModule {
const providers: Provider[] = [
{{#apiInfo}}
{{#apis}}
{
provide: {{classname}},
useClass: apiImplementations.{{#lambda.camelcase}}{{classname}}{{/lambda.camelcase}}
},

{{/apis}}
{{/apiInfo}}
];

return {
module: ApiModule,
controllers: [
{{#apiInfo}}
{{#apis}}
{{classname}}Controller,
{{/apis}}
{{/apiInfo}}
],
providers: [...providers],
exports: [...providers]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { {{#tsImports}}{{classname}}, {{/tsImports}} } from '../{{modelPackage}}';

@Injectable()
export abstract class {{classname}} {
{{#operations}}
{{#operation}}
abstract {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}, {{/allParams}} request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}>;

{{/operation}}
{{/operations}}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{#apiInfo}}
{{#apis}}
{{#operations}}
export * from './{{classFilename}}';
{{/operations}}
{{/apis}}
{{/apiInfo}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Controller{{#httpMethods}}, {{.}}{{/httpMethods}}, Req } from '@nestjs/common';
import { Observable } from 'rxjs';
import { {{classname}} } from '../{{apiPackage}}';
import { {{#tsImports}}{{classname}}, {{/tsImports}} } from '../{{modelPackage}}';

@Controller()
export class {{classname}}Controller {
constructor(private readonly {{classVarName}}: {{classname}}) {}

{{#operations}}
{{#operation}}
@{{#vendorExtensions.x-http-method}}{{.}}{{/vendorExtensions.x-http-method}}{{^vendorExtensions.x-http-method}}{{httpMethod}}{{/vendorExtensions.x-http-method}}('{{path}}')
{{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}, {{/allParams}}@Req() request: Request): {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}} | Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> | Observable<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}> {
return this.{{classVarName}}.{{operationId}}({{#allParams}}{{paramName}}, {{/allParams}} request);
}

{{/operation}}
{{/operations}}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{#apiInfo}}
{{#apis}}
{{#operations}}
export * from './{{classFilename}}.controller';
{{/operations}}
{{/apis}}
{{/apiInfo}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

# Git push script for {{npmName}}

# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "Error: Not in a git repository"
exit 1
fi

# Get the current branch
CURRENT_BRANCH=$(git branch --show-current)

echo "Pushing to git repository..."
echo "Current branch: $CURRENT_BRANCH"

# Add all changes
git add .

# Commit with a default message if no commit message provided
if [ -z "$1" ]; then
git commit -m "Update generated NestJS server code"
else
git commit -m "$1"
fi

# Push to the current branch
git push origin $CURRENT_BRANCH

echo "Successfully pushed to git repository!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Storybook build outputs
.out
.storybook-out

# Temporary folders
tmp/
temp/
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{#models}}
{{#model}}
{{#tsImports}}
import { {{classname}} } from './{{filename}}';
{{/tsImports}}


{{#description}}
/**
* {{{.}}}
*/
{{/description}}
{{#isEnum}}{{>modelEnum}}{{/isEnum}}{{^isEnum}}{{#isAlias}}{{>modelAlias}}{{/isAlias}}{{^isAlias}}{{#taggedUnions}}{{>modelTaggedUnion}}{{/taggedUnions}}{{^taggedUnions}}{{#oneOf}}{{#-first}}{{>modelOneOf}}{{/-first}}{{/oneOf}}{{^oneOf}}{{>modelGeneric}}{{/oneOf}}{{/taggedUnions}}{{/isAlias}}{{/isEnum}}
{{/model}}
{{/models}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type {{classname}} = {{dataType}};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{{#stringEnums}}
export enum {{classname}} {
{{#allowableValues}}
{{#enumVars}}
{{#enumDescription}}

/**
* {{.}}
*/{{/enumDescription}}
{{name}} = {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
}
{{/stringEnums}}
{{^stringEnums}}
export const {{classname}} = {
{{#allowableValues}}
{{#enumVars}}
{{#enumDescription}}

/**
* {{.}}
*/
{{/enumDescription}}
{{name}}: {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
} as const;
export type {{classname}} = typeof {{classname}}[keyof typeof {{classname}}];
{{/stringEnums}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface {{classname}}{{#allParents}}{{#-first}} extends {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/allParents}} { {{>modelGenericAdditionalProperties}}
{{#vars}}
{{#description}}
/**
* {{{description}}}
{{#deprecated}}
* @deprecated
{{/deprecated}}
*/
{{/description}}
{{^description}}
{{#deprecated}}
/** @deprecated */
{{/deprecated}}
{{/description}}
{{^modelPropertyNamingOriginal}}
{{#isReadOnly}}readonly {{/isReadOnly}}{{{name}}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{/modelPropertyNamingOriginal}}
{{#modelPropertyNamingOriginal}}
{{#isReadOnly}}readonly {{/isReadOnly}}{{#hasSanitizedName}}'{{{baseName}}}'{{/hasSanitizedName}}{{^hasSanitizedName}}{{{name}}}{{/hasSanitizedName}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}} | null{{/isNullable}};
{{/modelPropertyNamingOriginal}}
{{/vars}}
}{{>modelGenericEnums}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{#additionalPropertiesType}}

[key: string]: {{{additionalPropertiesType}}}{{#hasVars}} | any{{/hasVars}};

{{/additionalPropertiesType}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{{#hasEnums}}

{{^stringEnums}}
export namespace {{classname}} {
{{/stringEnums}}
{{#vars}}
{{#isEnum}}
{{#stringEnums}}
export enum {{classname}}{{enumName}} {
{{#allowableValues}}
{{#enumVars}}
{{name}} = {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
};
{{/stringEnums}}
{{^stringEnums}}
export const {{enumName}} = {
{{#allowableValues}}
{{#enumVars}}
{{name}}: {{{value}}}{{^-last}},{{/-last}}
{{/enumVars}}
{{/allowableValues}}
} as const;
export type {{enumName}} = typeof {{enumName}}[keyof typeof {{enumName}}];
{{/stringEnums}}
{{/isEnum}}
{{/vars}}
{{^stringEnums}}}{{/stringEnums}}
{{/hasEnums}}
Loading