Serverless ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ™˜๊ฒฝ์—์„œ API Docs ํ†ตํ•ฉํ•˜๊ธฐ

seungbin1206
  • #nestjs
  • #api docs
  • #swagger
  • #serverless
  • #webpack
  • #Dependabot
  • #microservice

API Docs


NestJS๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, @nestjs/swagger ๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์‰ฝ๊ฒŒ API Docs๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, ํ•˜๋‚˜์˜ ์„œ๋น„์Šค์—์„œ API Docs๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฑด ๋งค์šฐ ์‰ฌ์šด ์ผ์ž…๋‹ˆ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋Š” NestJs ๊ณต์‹ ๋ฌธ์„œ์— ์žˆ๋Š” @nestjs/swagger๋กœ Swagger UI๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

import {NestFactory} from '@nestjs/core';
import {SwaggerModule, DocumentBuilder} from '@nestjs/swagger';

import {AppModule} from './app.module';

async function bootstrap() {
    const expressApp = express();

    const app = await NestFactory.create(AppModule, new ExpressAdapter(expressApp));

    const config = new DocumentBuilder().setTitle('Cats example').setDescription('The cats API description').setVersion('1.0').addTag('cats').build();

    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api', app, document);

    await app.listen(3000);
}

bootstrap();

NestFactory์—์„œ AppModule์„ ์‚ฌ์šฉํ•ด app์„ ๋งŒ๋“ค๊ณ , ๊ทธ app๊ณผ swagger config๋ฅผ ์‚ฌ์šฉํ•ด์„œ document๋ฅผ ๋งŒ๋“ค๊ณ , ๋งˆ์ง€๋ง‰์œผ๋กœ setupํ•ด์ฃผ๋Š” ๊ณผ์ •์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ ํ”„๋กœ์ ํŠธ์—์„œ API Docs๋ฅผ ์ƒ์„ฑํ•˜๋Š”๊ฑด ๋งค์šฐ ์‰ฌ์šด ์ผ์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ์„œ๋น„์Šค๋ณ„๋กœ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์„ ๋• API Docs๋ฅผ ์–ด๋–ป๊ฒŒ ์ƒ์„ฑํ•ด์•ผ ํ• ๊นŒ์š”?

์ด ๊ธ€์—์„ , ์œ„ ์ฝ”๋“œ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์— ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๋Š” ๋ชจ๋“ˆ๋“ค์„ ํ•˜๋‚˜์˜ API Docs๋กœ ํ•ฉ์น˜๋Š” ๊ณผ์ •์„ ๋ณด์—ฌ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜ˆ์ œ๋Š” Express ๊ธฐ๋ฐ˜์ด์ง€๋งŒ, ๊ผญ Express ๊ธฐ๋ฐ˜์ด ์•„๋‹ˆ๋”๋ผ๋„ ๊ฐ๊ฐ์˜ ๊ธฐ๋ฐ˜์ด ๋˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์— ๋งž์ถฐ ์กฐ๊ธˆ์”ฉ๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ์ถฉ๋ถ„ํžˆ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


1. Module ์ƒ์„ฑ

์—ฌ๋Ÿฌ ์„œ๋น„์Šค์— ๋ถ„๋ฆฌ๋˜์–ด์žˆ๋Š” API Docs๋ฅผ ํ†ตํ•ฉํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ ๊ฐ€์žฅ ๋จผ์ € ํ•ด์•ผ ํ•  ์ผ์€, document๋ฅผ ๋งŒ๋“ค app์„ ํ•˜๋‚˜ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด ์ฃผ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  app์„ ์ƒˆ๋กœ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„ , ์ƒˆ๋กœ์šด ๋ชจ๋“ˆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋“ˆ์ด ๋ถ„๋ฆฌ๋œ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์˜ ๋ชจ๋“ˆ๋“ค์„ importํ•˜๋Š” ํ•˜๋‚˜์˜ ํฐ ๋ชจ๋“ˆ์ด ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

A ์„œ๋น„์Šค์˜ A Module๊ณผ B ์„œ๋น„์Šค์˜ B Module์„ ํ•˜๋‚˜์˜ docs๋กœ ํ•ฉ์นœ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์€ ๋ชจ๋“ˆ์ด ๋งŒ๋“ค์–ด ์งˆ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

@Module({
    imports: [AModule, BModule]
})
export class DocsModule {}

์ด ๋ชจ๋“ˆ์„ ๊ธฐ๋ฐ˜์œผ๋กœ bootstrap ํ•จ์ˆ˜์—์„œ Swagger๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฉ๋‹ˆ๋‹ค.

import {NestFactory} from '@nestjs/core';
import {SwaggerModule, DocumentBuilder} from '@nestjs/swagger';

import {AppModule, DocsModule} from './app.module';

async function bootstrap() {
    const expressApp = express();

    const app = await NestFactory.create(AppModule); // ์„œ๋ฒ„ ์‹คํ–‰์„ ์œ„ํ•œ AppModule
    const docsApp = await NestFactory.create(DocsModule); // Docs ์ƒ์„ฑ์„ ์œ„ํ•œ DocsModule

    const config = new DocumentBuilder().setTitle('Cats example').setDescription('The cats API description').setVersion('1.0').addTag('cats').build();

    const document = SwaggerModule.createDocument(docsApp, config); // Docs ์ƒ์„ฑ์—” docsApp ์‚ฌ์šฉ
    // SwaggerModule.setup('api', docsApp, document); // Docs ์ƒ์„ฑ์€ Api๋กœ ์ง„ํ–‰ํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, setup์€ ์ œ๊ฑฐ

    await app.listen(3000); // ์„œ๋ฒ„ ์‹คํ–‰์—” app ์‚ฌ์šฉ
}

bootstrap();

2. SwaggerModule.setup ์ง์ ‘ ๊ตฌํ˜„

๋‘ ๋ฒˆ์งธ ๊ณผ์ •์„ ์„ค๋ช…ํ•˜๊ธฐ ์ „์—, SwaggerModule์˜ setup ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€๋ถ€ํ„ฐ ์•Œ์•„์•ผ ํ•  ํ•„์š”๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

SwaggerModule์˜ ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด,

...
class SwaggerModule {
    static createDocument(app, config, options = {}) {
        const swaggerScanner = new swagger_scanner_1.SwaggerScanner();
        const document = swaggerScanner.scanApplication(app, options);
        document.components = Object.assign(Object.assign({}, (config.components || {})), document.components);
        return Object.assign(Object.assign({ openapi: '3.0.0' }, config), document);
    }
    static setup(path, app, document, options) {
        const httpAdapter = app.getHttpAdapter();
        if (httpAdapter && httpAdapter.getType() === 'fastify') {
            return this.setupFastify(path, httpAdapter, document);
        }
        return this.setupExpress(path, app, document, options);
    }
    static setupExpress(path, app, document, options) {
        const httpAdapter = app.getHttpAdapter();
        const finalPath = validate_path_util_1.validatePath(path);
        const swaggerUi = load_package_util_1.loadPackage('swagger-ui-express', 'SwaggerModule', () => require('swagger-ui-express'));
        const swaggerHtml = swaggerUi.generateHTML(document, options);
        app.use(finalPath, swaggerUi.serveFiles(document, options));
        httpAdapter.get(finalPath, (req, res) => res.send(swaggerHtml));
        httpAdapter.get(finalPath + '-json', (req, res) => res.json(document));
    }
    static setupFastify(path, httpServer, document) {
        const hasParserGetterDefined = Object.getPrototypeOf(httpServer).hasOwnProperty('isParserRegistered');
        if (hasParserGetterDefined && !httpServer.isParserRegistered) {
            httpServer.registerParserMiddleware();
        }
        httpServer.register((httpServer) => __awaiter(this, void 0, void 0, function* () {
            httpServer.register(load_package_util_1.loadPackage('fastify-swagger', 'SwaggerModule', () => require('fastify-swagger')), {
                swagger: document,
                exposeRoute: true,
                routePrefix: path,
                mode: 'static',
                specification: {
                    document
                }
            });
        }));
    }
}
...

์ด๋Ÿฐ ํด๋ž˜์Šค ํ•˜๋‚˜๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋˜๋Š”๋ฐ, ์—ฌ๊ธฐ์„œ setup์€ ์‚ฌ์‹ค์ƒ httpAdapter์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š”๊ฒŒ ์ „๋ถ€์ด๋ฏ€๋กœ, setupExpress, setupFastify๊ฐ€ API Docs๋ฅผ ๊ตฌ์„ฑํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

httpAdapter๊ฐ€ ์—†์„ ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ setupExpress๋ฅผ ํ˜ธ์ถœํ•˜๋ฏ€๋กœ, Express๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ด์•ผ๊ธฐ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

setupExpress๊ฐ€ ํ•˜๋Š” ์ผ์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๋“ค์–ด์˜จ path์— swaggerUi.serveFiles ํ•ด์ฃผ๋Š” ๋ฏธ๋“ค์›จ์–ด ์ƒ์„ฑ
  2. ๋“ค์–ด์˜จ path์— get ์š”์ฒญ์„ ๋ณด๋‚ผ ์‹œ swaggerUi.generateHTML์˜ ๊ฒฐ๊ณผ๊ฐ’์„ sendํ•ด์ฃผ๋Š” API ์ƒ์„ฑ

์ด ๋‘ ๊ฐ€์ง€๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์ฃผ๋ฉด API ํ˜ธ์ถœ์‹œ Swagger UI๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์šฐ์„ , ๋ฏธ๋“ค์›จ์–ด๋Š” 1๋ฒˆ ๊ณผ์ •์—์„œ ๋งŒ๋“  bootstrap ํ•จ์ˆ˜์— ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

import {NestFactory} from '@nestjs/core';
import {SwaggerModule, DocumentBuilder} from '@nestjs/swagger';
import SwaggerUi from 'swagger-ui-express';

import {AppModule, DocsModule} from './app.module';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    const docsApp = await NestFactory.create(DocsModule);

    const config = new DocumentBuilder().setTitle('Cats example').setDescription('The cats API description').setVersion('1.0').addTag('cats').build();

    const document = SwaggerModule.createDocument(docsApp, config);

    app.use('/docs', SwaggerUi.serveFiles(docsApp)); // /docs ๊ฒฝ๋กœ์— ์ •์ ํŒŒ์ผ ์ œ๊ณต ๋ฏธ๋“ค์›จ์–ด ์ถ”๊ฐ€

    await app.listen(3000);
}

bootstrap();

๊ทธ ๋‹ค์Œ generateHTML์€, ํ•ด๋‹น ๋ฏธ๋“ค์›จ์–ด์™€ ๊ฐ™์€ ๊ฒฝ๋กœ์˜ API๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฆฌํ„ด๋งŒ ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

controller์™€ module์€ ์ƒ๋žตํ•˜๊ณ  service์˜ ์ฝ”๋“œ๋งŒ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐ ๋ฉ๋‹ˆ๋‹ค.

import SwaggerUi from 'swagger-ui-express';

@Injectable()
export class DocsService {
    getSwaggerHTML() {
        return SwaggerUi.generateHTML();
    }
}

์ด์ œ ๋กœ์ปฌ์—์„œ ์„œ๋ฒ„ ์‹คํ–‰ ํ›„ localhost:3000/docs๋กœ ์ ‘์†ํ•˜๋ฉด API Docs๊ฐ€ ๋ณด์ผ ๊ฒƒ์ž…๋‹ˆ๋‹ค.


3. Serverless Framework๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, Api Event์— ์ •์  ํŒŒ์ผ ๊ฒฝ๋กœ ์ถ”๊ฐ€

๋กœ์ปฌ์—์„œ ์‹คํ–‰ํ–ˆ์„ ๋•Œ ๋ฌธ์ œ๊ฐ€ ์—†์—ˆ์œผ๋‹ˆ, Serverless๋ฅผ ์‚ฌ์šฉํ•œ ๋ฐฐํฌ์—์„œ๋„ ์ž˜ ๋‚˜์˜ฌ๊ฑฐ๋ผ ์ƒ๊ฐ ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฐฐํฌ ํ›„ /docs ๊ฒฝ๋กœ์— ์ ‘๊ทผํ•ด๋ณด๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ API Docs๊ฐ€ ๋ณด์ด์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์ผœ์„œ ํ™•์ธํ•ด๋ณด๋‹ˆ, ์ •์  ํŒŒ์ผ๋“ค์„ get ํ•˜๋Š” ๊ณผ์ •์—์„œ ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

serverless.yml์˜ events ์•ˆ์˜ ์ •์  ํŒŒ์ผ๋“ค ๊ฒฝ๋กœ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค˜์•ผ, ์ œ๋Œ€๋กœ docs๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

functions:
    Docs:
        handler: src/docs/lambda.handler
        events:
            - http:
                  path: /v2/docs
                  method: get
            - http:
                  path: /v2/docs/swagger-ui.css
                  method: get
            - http:
                  path: /v2/docs/swagger-ui-bundle.js
                  method: get
            - http:
                  path: /v2/docs/swagger-ui-standalone-preset.js
                  method: get
            - http:
                  path: /v2/docs/swagger-ui-init.js
                  method: get
            - http:
                  path: /v2/docs/favicon-32x32.png
                  method: get
            - http:
                  path: /v2/docs/favicon-16x16.png
                  method: get
            - http:
                  path: /v2/docs/favicon.ico
                  method: get

3 - 1. Webpack์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ์ •์ ํŒŒ์ผ copy ๋ฐ ๋ฏธ๋“ค์›จ์–ด ๊ฒฝ๋กœ ์ˆ˜์ •

Webpack์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด swagger-ui.css is not found ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด์œ ๋ฅผ ์•Œ๋ ค๋ฉด, serveFiles๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

swagger-ui-express์˜ serveFiles๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด,

...

var swaggerUi = require('swagger-ui-dist')

...

var swaggerAssetMiddleware = options => {
    var opts = options || {};
    opts.index = false;
    return express.static(swaggerUi.getAbsoluteFSPath(), opts);
};

var serveFiles = function (swaggerDoc, opts) {
    opts = opts || {};
    var initOptions = {
        swaggerDoc: swaggerDoc || undefined,
        customOptions: opts.swaggerOptions || {},
        swaggerUrl: opts.swaggerUrl || {},
        swaggerUrls: opts.swaggerUrls || undefined
    };
    var swaggerInitWithOpts = swaggerInitFunction(swaggerDoc, initOptions);
    return [swaggerInitWithOpts, swaggerAssetMiddleware()];
};

...

์ •์ ํŒŒ์ผ์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ํ•˜๋‚˜ ์žˆ๊ณ , ์ด ์ •์  ํŒŒ์ผ๋“ค์„ ๊ฐ€์ ธ์˜ฌ ๊ฒฝ๋กœ ๋ฐ ์ •์  ํŒŒ์ผ๋“ค์ด swagger-ui-dist ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ์ •์˜๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

404 Not Found ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋Š”, ์ •์  ํŒŒ์ผ์„ ๊ฐ€์ ธ์™€์•ผ ํ•  ๊ฒฝ๋กœ์— ํ•ด๋‹น ํŒŒ์ผ๋“ค์ด ์—†์–ด์„œ ๋‚˜๋Š” ์˜ค๋ฅ˜์˜€๊ณ , ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋‘ ๊ฐ€์ง€ ๊ณผ์ •์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.

  1. webpack.config.js ํŒŒ์ผ์— CopyWebpackPlugin ์‚ฌ์šฉ
  2. /docs ์˜ serveFiles ๋ฏธ๋“ค์›จ์–ด ๊ฒฝ๋กœ ์ˆ˜์ •

์šฐ์„  1๋ฒˆ ๊ณผ์ •์€, Webpack์œผ๋กœ ๋นŒ๋“œํ•˜๋Š” ๊ณผ์ •์—์„œ swagger-ui-dist์˜ ์ •์  ํŒŒ์ผ๋“ค์ด ๋นŒ๋“œ๋œ ๋””๋ ‰ํ† ๋ฆฌ์— ์กด์žฌํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•œ ๊ณผ์ •์ด์—ˆ์Šต๋‹ˆ๋‹ค.

...

plugins: [
        new CopyWebpackPlugin({
            patterns: [
                './node_modules/swagger-ui-dist/swagger-ui.css',
                './node_modules/swagger-ui-dist/swagger-ui-bundle.js',
                './node_modules/swagger-ui-dist/swagger-ui-standalone-preset.js',
                './node_modules/swagger-ui-dist/swagger-ui-init.js',
                './node_modules/swagger-ui-dist/favicon-32x32.png',
                './node_modules/swagger-ui-dist/favicon-16x16.png',
                './node_modules/swagger-ui-dist/favicon.ico',
                '.env'
            ]
        }),
        new webpack.IgnorePlugin(WebPackIgnorePlugin)
    ],

...

์ด๋Ÿฐ ์‹์œผ๋กœ webpack.config.js plugins์—์„œ ์ •์  ํŒŒ์ผ๋“ค์„ copy ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2๋ฒˆ ๊ณผ์ •์€, ์ €๋ ‡๊ฒŒ copy๋ฅผ ํ•ด๋„ ๊ธฐ์กด ๋ฏธ๋“ค์›จ์–ด์˜ ๊ฒฝ๋กœ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์นดํ”ผ๋œ ํŒŒ์ผ๋“ค์„ ์ฐพ์„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

app.use('/v2/docs', express.static(path.join(__dirname, '..', '..')), SwaggerUi.serveFiles(document, customOptions));

express.static()์œผ๋กœ ์ •์ ํŒŒ์ผ๋“ค์„ ๊ฐ€์ ธ ์˜ฌ ๊ฒฝ๋กœ๋ฅผ ์žฌ์„ค์ • ํ•ด์ค˜์•ผ ์ œ๋Œ€๋กœ ์ •์  ํŒŒ์ผ๋“ค์ด import ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.



Dependabot

์ƒˆ๋กœ์šด API๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ ๋งˆ๋‹ค ๊ฐ ํผ๋ธ”๋ฆฌ์‹œ๋œ ํŒจํ‚ค์ง€๋“ค์„ ์ƒˆ๋กœ ์„ค์น˜ํ•˜๊ณ , ๋ฐฐํฌํ•ด์ค˜์•ผ ํ•œ๋‹ค๋Š” ๋ถˆํŽธํ•จ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด ๋ถˆํŽธํ•จ ๋•Œ๋ฌธ์— ์ €ํฌ ๊ฐœ๋ฐœํŒ€์ด ์ƒ๊ฐํ•ด ๋‚ธ ๊ฑด, GitHub์˜ Dependabot์ด๋ผ๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.

Dependabot์€ ์„ค์น˜๋˜์–ด์žˆ๋Š” ํŒจํ‚ค์ง€, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๋ฒ„์ „์€ ์ž๋™์œผ๋กœ ์ฒดํฌํ•˜๊ณ , ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ Pull Request๋ฅผ ์ƒ์„ฑํ•ด ์ค๋‹ˆ๋‹ค.

์ด๋ฅผ ์ด์šฉํ•ด์„œ ์ƒˆ๋กœ์šด API๊ฐ€ ์ถ”๊ฐ€๋  ๋•Œ ๋งˆ๋‹ค Dependabot์ด ์ด๋ฅผ ๊ฐ์ง€ํ•ด์„œ Pull Request๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์šฐ๋ฆฌ๋Š” ๊ฐ„๋‹จํžˆ ํ™•์ธ ํ›„ Merge๋งŒ ํ•˜๋ฉด ๋˜๋Š” ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

Dependabot๋„ ๋‹ค๋ฅธ GitHub Actions์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ .github ํด๋” ๋‚ด์— Dependabot.yml ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์—ฌ GitHub์— ํ‘ธ์‹œํ•ด๋‘๋ฉด, Dependabot์ด ์ž‘๋™ํ•˜๋„๋ก ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

version: 2

updates:
    - package-ecosystem: 'npm'
      directory: '/'
      schedule:
          interval: 'daily'
          time: '00:00'
      open-pull-requests-limit: 30
      allow:
          - dependency-name: '@package/*'
      ignore:
          - dependency-name: '@package/ignore_package'
          - dependency-name: '*'
            update-types: ['version-update:semver-patch']
      target-branch: 'master'

์œ„ ์ฝ”๋“œ๋Š” ํ˜„์žฌ Wonderwall API Docs์—์„œ ์‚ฌ์šฉ์ค‘์ธ Dependabot.yml ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์„ค๋ช…์„ ํ•˜์ž๋ฉด

  1. schedule: Dependabot์ด ์ž‘๋™ํ•  ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ๋งŒ๋“œ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. interval daily๋Š” ๋งค์ผ (์ฃผ๋ง ์ œ์™ธ), time 00:00 ์€ UTC ๊ธฐ์ค€ 00์‹œ 00๋ถ„์— ๋Œ์•„๊ฐ„๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

  2. allow: Dependabot์ด ๋ฒ„์ „์—…์„ ๊ฐ์ง€ํ•  ํŒจํ‚ค์ง€๋ฅผ ์ง€์ •ํ•ด์ฃผ๋Š” ๋ถ€๋ถ„์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ํŒจํ‚ค์ง€์— ๋Œ€ํ•œ ๋ฒ„์ „์—…์„ ๊ฐ์ง€ํ•ฉ๋‹ˆ๋‹ค. @packege/*์€ @package/ ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ชจ๋“  ํŒจํ‚ค์ง€๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.

  3. ignore: Dependabot์ด ๋ฒ„์ „์—…์„ ๋ฌด์‹œํ•  ํŒจํ‚ค์ง€๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค. @packege/*๋ฅผ ํ—ˆ์šฉํ•ด ์ฃผ๊ณ , ๊ทธ ์ค‘ ๋ฌด์‹œํ•  ํŒจํ‚ค์ง€๋ฅผ ์ง€์ •ํ•ด ์ค๋‹ˆ๋‹ค.
    ํŒจํ‚ค์ง€๋ฅผ ignore ํ•  ์ˆ˜๋„ ์žˆ๊ณ , update-types์—์„œ ๋ฒ„์ „์ด ์˜ค๋ฅด๋Š” ํŒจํ„ด์„ ๋ฌด์‹œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ version-update:semver-patch๋Š” ์‹œ๋ฉ˜ํ‹ฑ ๋ฒ„์ €๋‹์„ ๊ธฐ์ค€์œผ๋กœ patch ๋ฒ„์ „์ด ์—…๋ฐ์ดํŠธ ๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ๋ฌด์‹œํ•ฉ๋‹ˆ๋‹ค.

  4. target-branch: Pull Request๋ฅผ ์ƒ์„ฑํ•ด ์ค„ ๋ธŒ๋žœ์น˜๋ฅผ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค.

์ด ์™ธ์˜ ๋‹ค๋ฅธ ์˜ต์…˜์€ GitHub Docs์˜ Dependabot ์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Dependabot์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ์˜ ๋ฌธ์ œ๋Š”, ๋ฒ„์ „์—… ๋œ ํŒจํ‚ค์ง€๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ์ผ ๋•Œ Pull Request๊ฐ€ ํŒจํ‚ค์ง€์˜ ๊ฐœ์ˆ˜๋Œ€๋กœ ์ƒ์„ฑ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์—ฌ๋Ÿฌ๊ฐœ์˜ Pull Request๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์น˜๋Š” ์ž‘์—…์ด ์ถ”๊ฐ€๋˜์–ด์•ผ ํ–ˆ๊ณ  combine Dependabot prs workflow ๋ฅผ ์ฐพ์•˜์Šต๋‹ˆ๋‹ค.

Repository์— ์žˆ๋Š” ymlํŒŒ์ผ์„ .github ํด๋”์— ๋„ฃ๊ณ , README.md ํŒŒ์ผ์— ์ ํ˜€์žˆ๋Š”๋Œ€๋กœ ๋”ฐ๋ผ๊ฐ€๋ฉด ๋ชจ๋“  Dependabot Pull Request๋ฅผ ํ•ฉ์นœ ํ•˜๋‚˜์˜ Pull Request๋ฅผ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค.


๋งˆ๋ฌด๋ฆฌ

์›๋ž˜๋Š” API Docs๋ฅผ ํ•ฉ์น˜๋ฉฐ ํ–ˆ๋˜ ๊ณ ๋ฏผ๋“ค ์œ„์ฃผ์˜ ๊ธ€์„ ์ ์œผ๋ ค๊ณ  ํ–ˆ๋Š”๋ฐ, ์ ๋‹ค๋ณด๋‹ˆ @nestjs/swagger ํŒŒํ—ค์น˜๊ธฐ ๋Š๋‚Œ์˜ ๊ธ€์ด ๋‚˜์˜ค๊ฒŒ ๋˜์—ˆ๋„ค์š”.

๊ทธ๋ ‡๊ฒŒ ์–ด๋ ค์šด ๋‚ด์šฉ์€ ์•„๋‹ˆ์ง€๋งŒ, ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง์ ‘ ํ™•์ธํ•˜๊ณ  ๋ถ„์„ํ•˜๋Š๋ผ ๋งŽ์€ ์‚ฝ์งˆ์„ ํ–ˆ๋˜ ์ž‘์—…์ด์—ˆ์Šต๋‹ˆ๋‹ค.

์ €์™€ ๊ฐ™์€ ๊ณ ๋ฏผ์„ ํ•˜๊ณ  ๊ณ„์‹  ๋ถ„๊ป˜ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ

Art Changes Life

๋…ธ๋จธ์Šค์™€ ํ•จ๊ป˜ ์—”ํ„ฐํ…Œํฌ ์‚ฐ์—…์„ ํ˜์‹ ํ•ด๋‚˜๊ฐˆ ๋ฉค๋ฒ„๋ฅผ ์ฐพ์Šต๋‹ˆ๋‹ค.

์ฑ„์šฉ ์ค‘์ธ ๊ณต๊ณ  ๋ณด๊ธฐ