Reputation: 165
Nestjs swagger ui not loading styles when deployed to vercel but works well locally
I added vercel.json with the following configuration and deployed to vercel.
{
"version": 2,
"builds": [
{
"src": "src/main.ts",
"use": "@vercel/node"
}
],
"routes": [
{
"src": "/(.*)",
"dest": "src/main.ts",
"methods": ["GET", "POST", "PUT", "PATCH", "DELETE"]
}
]
}
main.ts
const swaggerConfig = new DocumentBuilder()
.setTitle('Tansfun')
.setDescription('API for Tansfun')
.setVersion('1.0')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'APIKey',
name: 'APIKey',
description: 'Enter API Key',
in: 'header',
},
'APIKey-auth',
)
.build();
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const document = SwaggerModule.createDocument(app, swaggerConfig);
app.useGlobalPipes(new ValidationPipe());
SwaggerModule.setup('api', app, document);
await app.listen(port);
}
bootstrap();
I used @nestjs/swagger v6
Upvotes: 4
Views: 4844
Reputation: 101
try with this, set the custom js and css
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// swagger setup
const config = new DocumentBuilder()
.setTitle('Backend Generator')
.setDescription('Documentation API Test')
.setVersion('1.0')
.setBasePath('api/v1')
.addBearerAuth({ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' })
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('swagger', app, document, {
customSiteTitle: 'Backend Generator',
customfavIcon: 'https://avatars.githubusercontent.com/u/6936373?s=200&v=4',
customJs: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.js',
],
customCssUrl: [
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.css',
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.css',
],
});
const cors = { ...CorsConfig };
app.enableCors(cors);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.setGlobalPrefix('api/v1');
useContainer(app.select(AppModule), { fallbackOnErrors: true });
await app.listen(5000);
}
bootstrap();
Upvotes: 10
Reputation: 534
I recently came across this issue. Fortunately I found a working working solution The solution is a bit hacky tho
Is to get you api's swagger JSON file host it and use it with a swagger ui explorer
NODE_ENV
variable to development.In your .env file
NODE_ENV="development"
Create a static folder in your projects root. eg: swagger-static
Statically serve content of the swagger-static folder here is link to documentation on serving static files with nestjs
In your app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'swagger-static'),
serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
swagger.json
file in the swagger-static folder in your prjects root directoryThis issue on github discusses and has a solution on how to generate a swagger JSON file for your api
Below is a code snippet on how to generate the swagger.json
file
In your main.ts
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { resolve } from 'path';
import { writeFileSync } from 'fs';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('/swagger', app, document);
await app.listen(process.env.PORT || 3000);
// get the swagger json file (if app is running in development mode)
if (process.env.NODE_ENV === 'development') {
const pathToSwaggerStaticFolder = resolve(process.cwd(), 'swagger-static');
// write swagger json file
const pathToSwaggerJson = resolve(
pathToSwaggerStaticFolder,
'swagger.json',
);
const swaggerJson = JSON.stringify(document, null, 2);
writeFileSync(pathToSwaggerJson, swaggerJson);
console.log(`Swagger JSON file written to: '/swagger-static/swagger.json'`);
}
}
bootstrap();
Now every time your app starts in development the swagger JSON file with the generated
In production it would be served on your vercel domain eg: https://yourprojectname.vercel.app/swagger/swagger.json
Push you changes to vercel and test your swagger api by using the path to the swagger.json file on your server
Eg: Head to the swagger ui explorer page https://petstore.swagger.io/?_ga=2.160760958.2144886769.1670328433-858019792.1670328433#/. On the the page enter the path to your swagger.json
file in the explorer input and click explore. Your swagger docs your now be loaded
Is to get missing swagger files in development and manually serve them statically on vercel (your production serve)
NODE_ENV
variable to development.In your .env file
NODE_ENV="development"
Create a static folder in your projects root. eg: swagger-static
Statically serve content of the swagger-static folder here is link to documentation on serving static files with nestjs
In your app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'swagger-static'),
serveRoot: process.env.NODE_ENV === 'development' ? '/' : '/swagger',
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Every time your app starts in development you make and http call to fetch the missing swagger ui resources on your production server.
In my case the missing files were swagger-ui-bundle.js
, swagger-ui-init.js
, swagger-ui-standalone-preset.js
and swagger-ui.css
In your main.ts file after your app has started check if your app is in development an fetch the missing swagger resources then store them in the swagger-static folder in your root directory
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
// core
import { resolve } from 'path';
import { writeFileSync, createWriteStream } from 'fs';
import { get } from 'http';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Cats example')
.setDescription('The cats API description')
.setVersion('1.0')
.addTag('cats')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('/swagger', app, document);
await app.listen(process.env.PORT || 3000);
// get the swagger json file (if app is running in development mode)
if (process.env.NODE_ENV === 'development') {
// write swagger ui files
get(
`${serverUrl}/swagger/swagger-ui-bundle.js`, function
(response) {
response.pipe(createWriteStream('swagger-static/swagger-ui-bundle.js'));
console.log(
`Swagger UI bundle file written to: '/swagger-static/swagger-ui-bundle.js'`,
);
});
get(`${serverUrl}/swagger/swagger-ui-init.js`, function (response) {
response.pipe(createWriteStream('swagger-static/swagger-ui-init.js'));
console.log(
`Swagger UI init file written to: '/swagger-static/swagger-ui-init.js'`,
);
});
get(
`${serverUrl}/swagger/swagger-ui-standalone-preset.js`,
function (response) {
response.pipe(
createWriteStream('swagger-static/swagger-ui-standalone-preset.js'),
);
console.log(
`Swagger UI standalone preset file written to: '/swagger-static/swagger-ui-standalone-preset.js'`,
);
});
get(`${serverUrl}/swagger/swagger-ui.css`, function (response) {
response.pipe(createWriteStream('swagger-static/swagger-ui.css'));
console.log(
`Swagger UI css file written to: '/swagger-static/swagger-ui.css'`,
);
});
}
}
bootstrap();
Now every time your app starts in development the missing swagger would be fetched localy and stored in the swagger-static folder
In production file missing would be served upon request on your vercel server
Push you changes to vercel and test your swagger. Everything should be working now
Upvotes: 5