<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>천천히 앞으로!</title>
    <link>https://developerjjh.tistory.com/</link>
    <description>방문해주신 모든 분들께 감사합니다, 참고가 되시길 바랄게요~</description>
    <language>ko</language>
    <pubDate>Sat, 11 Jul 2026 20:16:15 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JJeongHyun</managingEditor>
    <image>
      <title>천천히 앞으로!</title>
      <url>https://tistory1.daumcdn.net/tistory/5595783/attach/532dbdc600054867b961ae98e792db5e</url>
      <link>https://developerjjh.tistory.com</link>
    </image>
    <item>
      <title>(Node.js) Nestjs + MSA 기반 서버 구축 (3) - 게시물 CRUD</title>
      <link>https://developerjjh.tistory.com/213</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/212&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developerjjh.tistory.com/212&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750050083742&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(Node.js) Nestjs + MSA기반 서버 구축 (2) - MongoDB &amp;amp; Docker 연동&quot; data-og-description=&quot;2025.06.05 - [Node.js] - (Node.js) Nestjs + MSA 기반 서버 구축 (1) (Node.js) Nestjs + MSA 기반 서버 구축 (1)● 순서 요지환경 설정 및 Lib 설치코드 구현테스트 통신마무리 1. 요지우연히 NestJS + MSA + MongoDB를 기반&quot; data-og-host=&quot;developerjjh.tistory.com&quot; data-og-source-url=&quot;https://developerjjh.tistory.com/212&quot; data-og-url=&quot;https://developerjjh.tistory.com/212&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FKfa5/hyY76UdCuf/YlFcHPXGnvqZWVjOmVUrfK/img.png?width=800&amp;amp;height=66&amp;amp;face=0_0_800_66,https://scrap.kakaocdn.net/dn/bYPKMo/hyY8T78hrY/iKTRB7Ms7tY4qXwScdFee1/img.png?width=800&amp;amp;height=66&amp;amp;face=0_0_800_66,https://scrap.kakaocdn.net/dn/hfF42/hyY734fLVk/HRc37tXzSnPOp22jLEfku1/img.png?width=1855&amp;amp;height=918&amp;amp;face=0_0_1855_918&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/212&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerjjh.tistory.com/212&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FKfa5/hyY76UdCuf/YlFcHPXGnvqZWVjOmVUrfK/img.png?width=800&amp;amp;height=66&amp;amp;face=0_0_800_66,https://scrap.kakaocdn.net/dn/bYPKMo/hyY8T78hrY/iKTRB7Ms7tY4qXwScdFee1/img.png?width=800&amp;amp;height=66&amp;amp;face=0_0_800_66,https://scrap.kakaocdn.net/dn/hfF42/hyY734fLVk/HRc37tXzSnPOp22jLEfku1/img.png?width=1855&amp;amp;height=918&amp;amp;face=0_0_1855_918');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(Node.js) Nestjs + MSA기반 서버 구축 (2) - MongoDB &amp;amp; Docker 연동&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2025.06.05 - [Node.js] - (Node.js) Nestjs + MSA 기반 서버 구축 (1) (Node.js) Nestjs + MSA 기반 서버 구축 (1)● 순서 요지환경 설정 및 Lib 설치코드 구현테스트 통신마무리 1. 요지우연히 NestJS + MSA + MongoDB를 기반&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerjjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 순서&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요지&lt;/li&gt;
&lt;li&gt;기능 설명&lt;/li&gt;
&lt;li&gt;예제 코드&lt;/li&gt;
&lt;li&gt;테스트 통신 결과&lt;/li&gt;
&lt;li&gt;마무리&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 요지&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 게시물에서는 NestJS에 MSA 패턴을 적용한 후 데이터베이스 중 MongoDB를 연동하였다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 해당 요청에 따른 기능을 만들어보려고 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 기능 설명&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 게시물 전체 조회&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;등록되어 있는 모든 게시물을 조회한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 게시물 등록&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;게시물의 제목과 내용으로 요청&lt;/li&gt;
&lt;li&gt;제목은 필수 값이지만 내용은 없어도 등록되도록 설정&lt;/li&gt;
&lt;li&gt;제목은 중복을 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 게시물 수정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;게시물의 id값과 수정하고자 하는 내용을 요청&lt;/li&gt;
&lt;li&gt;해당 id값에 대한 존재여부도 확인하여 유효성 검사를 실시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;- 게시물 삭제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;수정과 마찬가지로 id값으로 삭제하고자 하는 게시물을 삭제&lt;/li&gt;
&lt;li&gt;존재하지 않는 게시물의 id값으로 요청 시 에러를 반환하도록 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 예제 코드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;gateway controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; API의 진입점으로 요청의 필수값에 대한 존재 여부를 검사&lt;/p&gt;
&lt;pre id=&quot;code_1750056798245&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// gateway/src/app.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Delete,
  Param,
} from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('boards')
  async getBoards() {
    return this.appService.getBoards();
  }

  @Post('board')
  async createBoard(@Body() data: { title: string; text?: string }) {
    if (!data.title) throw new Error('Title is required for creating a board');
    return this.appService.createBoard(data);
  }

  @Patch('board/update/:id')
  async updateBoard(
    @Param('id') id: string,
    @Body() data: { title: string; text?: string },
  ) {
    if (!id || !data.title) {
      throw new Error('ID and title are required for updating a board');
    }
    return this.appService.updateBoard(id, data);
  }

  @Delete('board/delete/:id')
  async deleteBoard(@Param('id') id: string) {
    if (!id) throw new Error('ID is required for deleting a board');

    return this.appService.deleteBoard(id);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;gateway service&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- board 서비스와 TCP 연결 후 일치하는 통신을 요청&lt;/p&gt;
&lt;pre id=&quot;code_1750056847631&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// gateway/src/app.service.ts

import { Injectable } from '@nestjs/common';
import {
  ClientProxy,
  ClientProxyFactory,
  Transport,
} from '@nestjs/microservices';

@Injectable()
export class AppService {
  private boardClient: ClientProxy;

  constructor() {
    this.boardClient = ClientProxyFactory.create({
      transport: Transport.TCP,
      options: { host: '0.0.0.0', port: 4002 },
    });
  }

  getHello(): string {
    return 'Hello World!';
  }

  getBoards() {
    return this.boardClient.send({ cmd: 'get-boards' }, {});
  }

  createBoard(data: any) {
    return this.boardClient.send({ cmd: 'create-board' }, data);
  }

  updateBoard(id: string, data: { title: string; text?: string }) {
    return this.boardClient.send({ cmd: 'update-board' }, { id, ...data });
  }

  deleteBoard(id: string) {
    return this.boardClient.send({ cmd: 'delete-board' }, { id });
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;board controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- gateway에서 TCP 통신에 대한 service 메서드를 호출&lt;/p&gt;
&lt;pre id=&quot;code_1750057073490&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Controller } from '@nestjs/common';
import { MessagePattern, Payload } from '@nestjs/microservices';

import { AppService } from './app.service';
import { Board } from './schema/board.schema';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @MessagePattern({ cmd: 'get-boards' })
  getBoards() {
    return this.appService.findAll();
  }

  @MessagePattern({ cmd: 'create-board' })
  createBoard(@Payload() data: Partial&amp;lt;Board&amp;gt;) {
    return this.appService.create(data);
  }

  @MessagePattern({ cmd: 'update-board' })
  updateBoard(@Payload() data: { id: string; title: string; text?: string }) {
    return this.appService.updateBoard(data.id, {
      title: data.title,
      text: data.text,
    });
  }

  @MessagePattern({ cmd: 'delete-board' })
  deleteBoard(@Payload() data: { id: string }) {
    return this.appService.deleteBoard(data.id);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;board service&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 메서드 별 비즈니스 및 DB 로직을 작성&lt;/p&gt;
&lt;pre id=&quot;code_1750057112204&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';

import { Board } from './schema/board.schema';

@Injectable()
export class AppService {
  constructor(@InjectModel(Board.name) private boardModel: Model&amp;lt;Board&amp;gt;) {}

  async findAll(): Promise&amp;lt;Board[]&amp;gt; {
    return this.boardModel.find().exec();
  }

  async create(data: Partial&amp;lt;Board&amp;gt;): Promise&amp;lt;Board&amp;gt; {
    const isBoard = await this.boardModel.findOne({ title: data.title }).exec();
    if (isBoard) throw new Error('Board with this title already exists');

    if (!data.text) data.text = '';

    const createdBoard = new this.boardModel(data);

    await createdBoard.save();

    return createdBoard;
  }

  async updateBoard(id: string, data: Partial&amp;lt;Board&amp;gt;): Promise&amp;lt;Board | null&amp;gt; {
    const board = await this.boardModel.findById(id).exec();
    if (!board) throw new Error('Board not found');

    if (data.title) board.title = data.title;

    if (data.text) board.text = data.text;

    await board.save();

    return board;
  }

  async deleteBoard(id: string): Promise&amp;lt;Board | null&amp;gt; {
    const board = await this.boardModel.findByIdAndDelete(id).exec();

    if (!board) throw new Error('Board not found');

    return board;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;4. 테스트 통신 결과&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;전체 게시물 호출&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxy90n/btsOBDisEbm/REFzJkksKRYkkkOjqMVxRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxy90n/btsOBDisEbm/REFzJkksKRYkkkOjqMVxRK/img.png&quot; data-filename=&quot;스크린샷 2025-06-16 152808.png&quot; data-origin-height=&quot;800&quot; data-origin-width=&quot;981&quot; data-is-animation=&quot;false&quot; width=&quot;378&quot; height=&quot;308&quot; style=&quot;width: 55.2528%; margin-right: 10px;&quot; data-widthpercent=&quot;55.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxy90n/btsOBDisEbm/REFzJkksKRYkkkOjqMVxRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxy90n%2FbtsOBDisEbm%2FREFzJkksKRYkkkOjqMVxRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;981&quot; height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9pZlv/btsOCi518Bz/AAK3iTNeyaKPygMD4qlybK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9pZlv/btsOCi518Bz/AAK3iTNeyaKPygMD4qlybK/img.png&quot; data-filename=&quot;스크린샷 2025-06-16 152839.png&quot; data-origin-height=&quot;712&quot; data-origin-width=&quot;688&quot; data-is-animation=&quot;false&quot; width=&quot;435&quot; height=&quot;450&quot; style=&quot;width: 43.5395%;&quot; data-widthpercent=&quot;44.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9pZlv/btsOCi518Bz/AAK3iTNeyaKPygMD4qlybK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9pZlv%2FbtsOCi518Bz%2FAAK3iTNeyaKPygMD4qlybK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;688&quot; height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;전체 게시물 호출&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게시물 등록&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-16 152717.png&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yRAl7/btsOCxu5X0Q/JFp5WJCnUWyWkQKXqntEF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yRAl7/btsOCxu5X0Q/JFp5WJCnUWyWkQKXqntEF0/img.png&quot; data-alt=&quot;게시물 등록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yRAl7/btsOCxu5X0Q/JFp5WJCnUWyWkQKXqntEF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyRAl7%2FbtsOCxu5X0Q%2FJFp5WJCnUWyWkQKXqntEF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;629&quot; height=&quot;383&quot; data-filename=&quot;스크린샷 2025-06-16 152717.png&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게시물 등록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특정 게시물 수정&lt;/b&gt;&lt;br /&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Is1CI/btsOBCDW4PO/80xfZVQtjN5TtRwHIunSSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Is1CI/btsOBCDW4PO/80xfZVQtjN5TtRwHIunSSk/img.png&quot; style=&quot;width: 57.2505%; margin-right: 10px;&quot; data-widthpercent=&quot;57.95&quot; data-filename=&quot;스크린샷 2025-06-16 154837.png&quot; data-origin-height=&quot;599&quot; data-origin-width=&quot;990&quot; data-is-animation=&quot;false&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Is1CI/btsOBCDW4PO/80xfZVQtjN5TtRwHIunSSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIs1CI%2FbtsOBCDW4PO%2F80xfZVQtjN5TtRwHIunSSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xafkj/btsOCPoPb9h/fk1X0awAry7o41cDZC1pU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xafkj/btsOCPoPb9h/fk1X0awAry7o41cDZC1pU1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;813&quot; data-filename=&quot;스크린샷 2025-06-16 154830.png&quot; data-widthpercent=&quot;42.05&quot; style=&quot;width: 41.5418%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xafkj/btsOCPoPb9h/fk1X0awAry7o41cDZC1pU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxafkj%2FbtsOCPoPb9h%2Ffk1X0awAry7o41cDZC1pU1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;수정 결과 및 수정 후 전체 조회&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특정 게시물 삭제&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNDfz1/btsOCNEHflh/j6xQKEcGiI8FgkhsiWsnV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNDfz1/btsOCNEHflh/j6xQKEcGiI8FgkhsiWsnV1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;793&quot; data-filename=&quot;스크린샷 2025-06-16 155017.png&quot; style=&quot;width: 29.8718%; margin-right: 10px;&quot; data-widthpercent=&quot;30.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNDfz1/btsOCNEHflh/j6xQKEcGiI8FgkhsiWsnV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNDfz1%2FbtsOCNEHflh%2Fj6xQKEcGiI8FgkhsiWsnV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;990&quot; height=&quot;793&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zXqAS/btsOCOKa2QI/G35IfXTK0RY1kx4dD0u2LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zXqAS/btsOCOKa2QI/G35IfXTK0RY1kx4dD0u2LK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;599&quot; data-filename=&quot;스크린샷 2025-06-16 155029.png&quot; style=&quot;width: 39.1071%; margin-right: 10px;&quot; data-widthpercent=&quot;40.04&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zXqAS/btsOCOKa2QI/G35IfXTK0RY1kx4dD0u2LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzXqAS%2FbtsOCOKa2QI%2FG35IfXTK0RY1kx4dD0u2LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;979&quot; height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCvOmH/btsODJIltz8/crZVkiHraGqgRHRKXySIYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCvOmH/btsODJIltz8/crZVkiHraGqgRHRKXySIYk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;975&quot; data-origin-height=&quot;813&quot; data-filename=&quot;스크린샷 2025-06-16 154830.png&quot; style=&quot;width: 28.6955%;&quot; data-widthpercent=&quot;29.38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCvOmH/btsODJIltz8/crZVkiHraGqgRHRKXySIYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCvOmH%2FbtsODJIltz8%2FcrZVkiHraGqgRHRKXySIYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;975&quot; height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;삭제 전 전체 목록, 삭제 결과, 삭제 후 전체 목록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 최소한의 중복이나 존재 여부 정도만 검사하고 CRUD 하는 과정을 담아봤습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 부족한 점이 많지만 새로운 패턴과 환경 등을 적용하는 과정이니 참고 정도만 해주시면 감사하겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 또 다른 서비스(서버)를 연결하여 더 많은 기능들을 내포하는 프로젝트로 확장하는 게 목적입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 서비스를 별도로 생성하여서 로그인 상태를 검증하는 거를 생각하고 있으며 더 나아가 권한까지 부여하여 점차 커져가는 게 목표입니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;천천히 과정을 담아 볼테니 많은 관심 부탁드려요!!&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;025&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/025.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/025.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>Node.js</category>
      <category>docker</category>
      <category>docker compose</category>
      <category>nestjs</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/213</guid>
      <comments>https://developerjjh.tistory.com/213#entry213comment</comments>
      <pubDate>Sat, 21 Jun 2025 13:00:06 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) Nestjs + MSA기반 서버 구축 (2) - MongoDB &amp;amp; Docker 연동</title>
      <link>https://developerjjh.tistory.com/212</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/211&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.06.05 - [Node.js] - (Node.js) Nestjs + MSA 기반 서버 구축 (1)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1750049530177&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(Node.js) Nestjs + MSA 기반 서버 구축 (1)&quot; data-og-description=&quot;● 순서 요지환경 설정 및 Lib 설치코드 구현테스트 통신마무리 1. 요지우연히 NestJS + MSA + MongoDB를 기반으로 한 서버를 docker-compose로 구축할 수 있는 기회가 생겼었다그래서 해당 구축한 내용을 &quot; data-og-host=&quot;developerjjh.tistory.com&quot; data-og-source-url=&quot;https://developerjjh.tistory.com/211&quot; data-og-url=&quot;https://developerjjh.tistory.com/211&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bjc3ya/hyY8ZHgINb/KrrzfQPnsrvLnamjXYLNCk/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/TFa2e/hyY8Pq7DeM/P5KRhovOyaJ3Ir0v9EuET0/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bmTmA6/hyY8MuoI2f/1T2sKAEkkmsA17QOAMRb3k/img.png?width=1864&amp;amp;height=279&amp;amp;face=0_0_1864_279&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/211&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerjjh.tistory.com/211&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bjc3ya/hyY8ZHgINb/KrrzfQPnsrvLnamjXYLNCk/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/TFa2e/hyY8Pq7DeM/P5KRhovOyaJ3Ir0v9EuET0/img.png?width=800&amp;amp;height=432&amp;amp;face=0_0_800_432,https://scrap.kakaocdn.net/dn/bmTmA6/hyY8MuoI2f/1T2sKAEkkmsA17QOAMRb3k/img.png?width=1864&amp;amp;height=279&amp;amp;face=0_0_1864_279');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(Node.js) Nestjs + MSA 기반 서버 구축 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;● 순서 요지환경 설정 및 Lib 설치코드 구현테스트 통신마무리 1. 요지우연히 NestJS + MSA + MongoDB를 기반으로 한 서버를 docker-compose로 구축할 수 있는 기회가 생겼었다그래서 해당 구축한 내용을&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerjjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;● 순서&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MongoDB 설치&lt;/li&gt;
&lt;li&gt;docker 설치&lt;/li&gt;
&lt;li&gt;docker-compose로 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 게시물에 이어서 연결된 서버에 요청온 데이터들을 별도의 저장소인 DB내에 저장하는 과정을 기록하려고 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB는 MongoDB로 정해서 진행할 예정이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;MongoDB 및 몽구스 설치&lt;/p&gt;
&lt;pre id=&quot;code_1749278780414&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# nestjs 몽구스 모듈 설치
npm install @nestjs/mongoose mongoose&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;modules 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 해당 파일에서 몽구스를 이용해서 모듈을 추가&lt;/p&gt;
&lt;pre id=&quot;code_1749286228433&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// board/src/app.modules.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { BoardSchema } from './schema/board.scheme';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest'),
    MongooseModule.forFeature([{ name: 'Board', schema: BoardSchema }]),
  ],

  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Schema 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- mongoDB내 사용할 테이블의 스키마를 정의하는 파일 생성&lt;/p&gt;
&lt;pre id=&quot;code_1749286283306&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// board/src/schema/board.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';

export type BoardDocument = Board &amp;amp; Document;

@Schema()
export class Board {
  @Prop({ required: true })
  title: string;

  @Prop()
  text: string;
}

export const BoardSchema = SchemaFactory.createForClass(Board);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1749286636235&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# board 서비스 실행
npm run start:dev&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하니... 덜컥....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-06-07 171634.png&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7d0iC/btsOsUpLJzz/O69rpZz7DLWYPYME6v7s3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7d0iC/btsOsUpLJzz/O69rpZz7DLWYPYME6v7s3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7d0iC/btsOsUpLJzz/O69rpZz7DLWYPYME6v7s3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7d0iC%2FbtsOsUpLJzz%2FO69rpZz7DLWYPYME6v7s3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;109&quot; data-filename=&quot;스크린샷 2025-06-07 171634.png&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongoDB가 로컬에서 실행되고 있지 않아서 생긴 에러를 만날 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쩌지 어쩌지... 검색하고 찾아보고 알아보다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 각 서비스 마다 터미널을 열어서 실행했던 것도 불편하고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mongodb를 로컬에 설치해서 환경변수 설정 하는 등 까다로워서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한번에 실행하고 mongdb도 실행 할 수 있도록&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker와 docker-compose를 이용해보기로 했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Docker 설치 및 설정&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;docker desktop 프로그램을 설치&lt;/li&gt;
&lt;li&gt;docker-compose.yml 파일 생성&lt;/li&gt;
&lt;li&gt;각 서비스별 Dockerfile 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker desktop은 설치하는 법이 어렵지 않아서 간단히 설치를 해주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 예제는 다음과 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;docker-compose.yml&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 해당 파일은 프로젝트 최상단(root)에 생성&lt;/p&gt;
&lt;pre id=&quot;code_1749287084969&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# docker-compose.yml

services:
  gateway:
    build:
      context: ./gateway
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    depends_on:
      - board
    networks:
      - nest-net

  board:
    build:
      context: ./board
      dockerfile: Dockerfile
    ports:
      - '4002:4002'
    environment:
      - MONGO_URI=mongodb://${{mongodb이름}}:${{비밀번호}}@mongodb:27017/board?authSource=admin
    depends_on:
      - mongodb
    networks:
      - nest-net

  mongodb:
    image: mongo
    container_name: mongodb
    restart: always
    ports:
      - '27017:27017'
    environment:
      MONGO_INITDB_ROOT_USERNAME: ${{mongodb이름}}
      MONGO_INITDB_ROOT_PASSWORD: ${{비밀번호}}
    networks:
      - nest-net

networks:
  nest-net:
    driver: bridge&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;각 서비스별 Dockerfile&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해당 파일을 각 서비스별로 생성&lt;/p&gt;
&lt;pre id=&quot;code_1749287145220&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD [&quot;node&quot;, &quot;dist/main.js&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;node버전은 22&lt;/li&gt;
&lt;li&gt;컨데이너 내부에서 작업할 디렉토리를 /app으로 설정&lt;/li&gt;
&lt;li&gt;package.json과 lock.json을 복사&lt;/li&gt;
&lt;li&gt;의존성 설치&lt;/li&gt;
&lt;li&gt;빌드&lt;/li&gt;
&lt;li&gt;포트는 3000번으로 설정&lt;/li&gt;
&lt;li&gt;빌드내 dist/main.js로 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 왔다면 우리는 이제 실행을 해볼 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 명령어를 실행하기 전에 docker desktop은 실행해둬야 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1749287533790&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 프로젝트 최상단(root)
# docker-compose.yml 파일이 있는 곳
docker compose up --build
#혹은
docker-compose -up --build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 만일 mongodb를 설치하지 않았다면 아래의 docker 명령어로 설치&lt;/p&gt;
&lt;pre id=&quot;code_1749287614037&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -d --name mongodb -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=${{mongodb이름}} -e MONGO_INITDB_ROOT_PASSWORD=${{설정 비밀번호}} mongo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`docker-compose up --build` 명령어가 정상적으로 동작한다면 아래와 같은 화면을 볼 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzH3XW/btsOr5yRIQi/WUJuwj6jlPz815DrQkTvoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzH3XW/btsOr5yRIQi/WUJuwj6jlPz815DrQkTvoK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;826&quot; data-filename=&quot;스크린샷 2025-06-07 181428.png&quot; style=&quot;width: 41.3387%; margin-right: 10px;&quot; data-widthpercent=&quot;41.83&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzH3XW/btsOr5yRIQi/WUJuwj6jlPz815DrQkTvoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzH3XW%2FbtsOr5yRIQi%2FWUJuwj6jlPz815DrQkTvoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;826&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zIO8n/btsOsWnB4OA/CmeFXZ5kQfC1IwZCC8TXG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zIO8n/btsOsWnB4OA/CmeFXZ5kQfC1IwZCC8TXG1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1855&quot; data-origin-height=&quot;918&quot; data-filename=&quot;스크린샷 2025-06-07 181514.png&quot; style=&quot;width: 57.4985%;&quot; data-widthpercent=&quot;58.17&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zIO8n/btsOsWnB4OA/CmeFXZ5kQfC1IwZCC8TXG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzIO8n%2FbtsOsWnB4OA%2FCmeFXZ5kQfC1IwZCC8TXG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1855&quot; height=&quot;918&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;터미널과 docker desktop에서의 실행 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 화면에 찍히는 로그들은 같은 내용들이다, 본인이 좀 더 편한 화면으로 보면 좋을 듯 하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 설정하고 다음 게시물에서는 게시물을 등록해서 데이터베이스에서 확인하고 조회할 수 있도록 해보겠습니다~&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;018&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/018.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/018.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>Node.js</category>
      <category>docker</category>
      <category>docker compose</category>
      <category>nestjs</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/212</guid>
      <comments>https://developerjjh.tistory.com/212#entry212comment</comments>
      <pubDate>Wed, 11 Jun 2025 13:00:33 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) Nestjs + MSA 기반 서버 구축 (1)</title>
      <link>https://developerjjh.tistory.com/211</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;● 순서&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;요지&lt;/li&gt;&lt;li&gt;환경 설정 및 Lib 설치&lt;/li&gt;&lt;li&gt;코드 구현&lt;/li&gt;&lt;li&gt;테스트 통신&lt;/li&gt;&lt;li&gt;마무리&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 요지&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;우연히 NestJS + MSA + MongoDB를 기반으로 한 서버를 docker-compose로 구축할 수 있는 기회가 생겼었다&lt;br&gt;그래서 해당 구축한 내용을 기록하기 위해 작성해보고자 한다&lt;br&gt;단, 해당 내용이 아닌 다른 내용으로 기초 구축 단계만 작성하며, 단계 별로 나눠서 연동하고 부분적으로 업로드할 예정&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;MSA란?&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 마이크로서비스 아키텍처(Microservices Architecture)의 약자로 하나의 애플리케이션을 여러 개의 독립적인 서비스로 분리하여 운영하는 것을 의미한다&lt;br&gt;- NestJS는 MSA를 기본적으로 지원하여 서비스 간의 통신, API Gateway, 메시지 큐(RabbitMQ, Kafka)와의 통합 등을 쉽게 구현 가능&lt;br&gt;- 독립적인 개발, 배포, 확장이 될 수 있어서 유지보수 및 유연성에 편리&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUFxx7/btsOr2OtrPB/KZgKSceKwN29q5ypjo29fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUFxx7/btsOr2OtrPB/KZgKSceKwN29q5ypjo29fK/img.png&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;524&quot; style=&quot;width: 53.3606%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUFxx7/btsOr2OtrPB/KZgKSceKwN29q5ypjo29fK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUFxx7%2FbtsOr2OtrPB%2FKZgKSceKwN29q5ypjo29fK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;970&quot; height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G9gjU/btsOrz61UVk/zMpvU4zJkJ4GGGSS0KidLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G9gjU/btsOrz61UVk/zMpvU4zJkJ4GGGSS0KidLK/img.png&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;483&quot; style=&quot;width: 45.4766%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G9gjU/btsOrz61UVk/zMpvU4zJkJ4GGGSS0KidLK/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG9gjU%2FbtsOrz61UVk%2FzMpvU4zJkJ4GGGSS0KidLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;762&quot; height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;MSA의 구성 및 Gateway 예제&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버 구성&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 서버 구성은 다음과 같다&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt; 
 &lt;li&gt;Gateway 서버 - 모든 API의 진입점 
  &lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt; 
   &lt;li&gt;단일 진입점을 통해서 클라이언트 측에서 서비스 간의 관리와 네트워킹을 간소화&lt;/li&gt; 
   &lt;li&gt;보안 상의 위협으로부터 서비스를 보호&lt;/li&gt; 
   &lt;li&gt;서비스들 사이에서 요청을 분산하여 로드 밸런싱을 수행하여 서비스에 균등한 트래픽 분산을 제공하고 서비스의 가용성과 성능을 최적화&lt;/li&gt; 
   &lt;li&gt;API 관리 기능을 제공할 수 있습니다. API 버전 관리, 요청 및 응답의 변환, 캐싱, 로깅, 분석 등의 기능을 수행하여 개발자가 API를 효과적으로 관리하고 모니터링할 수 있도록 지원&lt;/li&gt; 
   &lt;li&gt;하나의 클라이언트 요청에 대해 여러 개의 서비스를 호출해야 하는 경우, Gateway 서버는 이러한 요청을 통합하여 단일 응답으로 반환하여, 클라이언트 측의 응답 처리를 간소화&lt;/li&gt; 
  &lt;/ul&gt; &lt;/li&gt; 
 &lt;li&gt;Board 서버 - 게시물 관련 CRUD&lt;/li&gt; 
&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- TCP를 이용한 Nest Microservices&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Nest에서 제공하는 라이브러리로 MSA를 구성&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 환경 설정 및 Lib 설치&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 먼저 NestJS를 전역에 설치&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# nestjs cli 전역 설치
npm install -global @nestjs/cli@latest

# 혹은
npm install -g @nestjs/cli@latest&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;- 구성할 서버(서비스) 명으로 구축 및 라이브러리 설치&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;# gateway 서비스 구축
nest new gateway

# gateway 라이브러리 설치
cd gateway
npm install @nestjs/microservices

# 프로젝트 root로 이동
cd .. 

# board 서비스 구축
nest new board
cd board
npm install @nestjs/microservices&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxWO0d/btsOpLVlBUl/y4XLPZDiGXUrnX3jCyugFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxWO0d/btsOpLVlBUl/y4XLPZDiGXUrnX3jCyugFk/img.png&quot; data-alt=&quot;설치 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxWO0d/btsOpLVlBUl/y4XLPZDiGXUrnX3jCyugFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxWO0d%2FbtsOpLVlBUl%2Fy4XLPZDiGXUrnX3jCyugFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;132&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;`nest new gateway` 혹은 `nest new board` 를 입력하면 위와 같은 화면을 만날 수 있다&lt;br&gt;나는 npm을 사용하기 위해 npm를 선택!!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cL0evT/btsOrqvCv2l/GEZDNCGxE0KSHIB1gQqJe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cL0evT/btsOrqvCv2l/GEZDNCGxE0KSHIB1gQqJe1/img.png&quot; data-alt=&quot;설치 완료&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cL0evT/btsOrqvCv2l/GEZDNCGxE0KSHIB1gQqJe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcL0evT%2FbtsOrqvCv2l%2FGEZDNCGxE0KSHIB1gQqJe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;467&quot; height=&quot;449&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;449&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;설치 완료&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;여기까지 성공했으면&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;cd gateway
npm run start:dev&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cegn3E/btsOqI4Q3xX/6zYZscnzj1LTKEprJA4pgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cegn3E/btsOqI4Q3xX/6zYZscnzj1LTKEprJA4pgK/img.png&quot; data-alt=&quot;npm 실행화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cegn3E/btsOqI4Q3xX/6zYZscnzj1LTKEprJA4pgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcegn3E%2FbtsOqI4Q3xX%2F6zYZscnzj1LTKEprJA4pgK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;148&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;npm 실행화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 http://localhost:3000으로 접속하면&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dNq2eV/btsOp4G0ELT/Y1XUVJkqheCYpU5aQmako1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dNq2eV/btsOp4G0ELT/Y1XUVJkqheCYpU5aQmako1/img.png&quot; data-alt=&quot;웹 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dNq2eV/btsOp4G0ELT/Y1XUVJkqheCYpU5aQmako1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdNq2eV%2FbtsOp4G0ELT%2FY1XUVJkqheCYpU5aQmako1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;315&quot; height=&quot;121&quot; data-origin-width=&quot;315&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;웹 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 화면을 만날 수 있다~!!&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 코드 구현&lt;/h4&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;3-1. gateway 서버&lt;br&gt;&lt;b&gt;gateway &lt;/b&gt;&lt;b&gt;controller&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- API 진입점을 정의&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// gateway/src/app.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
&amp;nbsp;&amp;nbsp;constructor(private readonly appService: AppService) {}

&amp;nbsp;&amp;nbsp;@Get()
&amp;nbsp;&amp;nbsp;getHello(): string {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.appService.getHello();
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@Get('boards')
&amp;nbsp;&amp;nbsp;async getBoards() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.appService.getBoards();
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@Post('board')
&amp;nbsp;&amp;nbsp;async createBoard(@Body() data) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.appService.createBoard(data);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;gateway service&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- 독립적인 서비스로 TCP 연결 후 통신 요청&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// gateway/src/app.service.ts
import { Injectable } from '@nestjs/common';
import {
&amp;nbsp;&amp;nbsp;ClientProxy,
&amp;nbsp;&amp;nbsp;ClientProxyFactory,
&amp;nbsp;&amp;nbsp;Transport,
} from '@nestjs/microservices';

@Injectable()
export class AppService {
&amp;nbsp;&amp;nbsp;private boardClient: ClientProxy;

&amp;nbsp;&amp;nbsp;constructor() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.boardClient = ClientProxyFactory.create({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transport: Transport.TCP,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;options: { host: '0.0.0.0', port: 4002 },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;});
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;getHello(): string {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return 'Hello World!';
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;getBoards() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.boardClient.send({ cmd: 'get-boards' }, {});
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;createBoard(data: any) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.boardClient.send({ cmd: 'create-board' }, data);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br&gt;3-2. board 서비스&lt;br&gt;&lt;b&gt;board main&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;- board에서도 TCP를 연결 설정&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// board/src/main.ts
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

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

async function bootstrap() {
&amp;nbsp;&amp;nbsp;const app = await NestFactory.createMicroservice&amp;lt;MicroserviceOptions&amp;gt;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppModule,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;transport: Transport.TCP,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;options: { host: '0.0.0.0', port: 4002 },
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;await app.listen();
}
bootstrap();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;board controller&lt;/b&gt;&lt;br&gt;- gateway에서 send메서드로 요청된 일치한 메시지패턴을 응답&lt;br&gt;- 비즈니스 로직 메서드를 호출&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// board/src/app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';

import { AppService } from './app.service';

@Controller()
export class AppController {
&amp;nbsp;&amp;nbsp;constructor(private readonly appService: AppService) {}

&amp;nbsp;&amp;nbsp;@MessagePattern({ cmd: 'get-boards' })
&amp;nbsp;&amp;nbsp;async getBoards() {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.appService.findAll();
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;@MessagePattern({ cmd: 'create-board' })
&amp;nbsp;&amp;nbsp;async createBoard(data: any) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return this.appService.create(data);
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;board service&lt;/b&gt;&lt;br&gt;- controller에서 호출된 비지니스 로직을 구현&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// board/src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
&amp;nbsp;&amp;nbsp;async findAll(): Promise&amp;lt;any[]&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// This should return a list of boards from the database
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// console.log('Fetching all boards');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return [];
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;async create(data: any): Promise&amp;lt;any&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// This should create a new board in the database
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// console.log('Creating a new board with data:', data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return data;
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;두 개의서버를 실행 후&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mOCzx/btsOqa8w353/E0sFdYkeSrG4kCrkqbkeL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mOCzx/btsOqa8w353/E0sFdYkeSrG4kCrkqbkeL0/img.png&quot; data-alt=&quot;npm run start:dev&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mOCzx/btsOqa8w353/E0sFdYkeSrG4kCrkqbkeL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmOCzx%2FbtsOqa8w353%2FE0sFdYkeSrG4kCrkqbkeL0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;279&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;npm run start:dev&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;- findAll 메서드나 create메서드에 가벼운 콘솔로 확인&lt;br&gt;- postman으로 통신 확인&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdS7ij/btsOqRmT657/7T6NmEXRpyZvJSi0DKmrKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdS7ij/btsOqRmT657/7T6NmEXRpyZvJSi0DKmrKK/img.png&quot; data-alt=&quot;localhost:3000 접속시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdS7ij/btsOqRmT657/7T6NmEXRpyZvJSi0DKmrKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdS7ij%2FbtsOqRmT657%2F7T6NmEXRpyZvJSi0DKmrKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;702&quot; height=&quot;416&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;localhost:3000 접속시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgqBfU/btsOqI42ore/SsAB1n5pOPK40thGg3BbS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgqBfU/btsOqI42ore/SsAB1n5pOPK40thGg3BbS0/img.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;687&quot; style=&quot;width: 33.7395%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgqBfU/btsOqI42ore/SsAB1n5pOPK40thGg3BbS0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgqBfU%2FbtsOqI42ore%2FSsAB1n5pOPK40thGg3BbS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;662&quot; height=&quot;687&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Kd1Oe/btsOqCDwQ0i/SgoJvxToGKXktfmSsWZr20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Kd1Oe/btsOqCDwQ0i/SgoJvxToGKXktfmSsWZr20/img.png&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;712&quot; style=&quot;width: 31.9155%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Kd1Oe/btsOqCDwQ0i/SgoJvxToGKXktfmSsWZr20/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKd1Oe%2FbtsOqCDwQ0i%2FSgoJvxToGKXktfmSsWZr20%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;712&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ppdEU/btsOqcSBigg/gLSL2PJwAghq6SCKBm4ts0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ppdEU/btsOqcSBigg/gLSL2PJwAghq6SCKBm4ts0/img.png&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;725&quot; style=&quot;width: 32.0194%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ppdEU/btsOqcSBigg/gLSL2PJwAghq6SCKBm4ts0/img.png&quot; alt=&quot;&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FppdEU%2FbtsOqcSBigg%2FgLSL2PJwAghq6SCKBm4ts0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;663&quot; height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;GET localhost:3000/boards&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/211</guid>
      <comments>https://developerjjh.tistory.com/211#entry211comment</comments>
      <pubDate>Sun, 8 Jun 2025 13:00:22 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) nodemailer로 파일첨부</title>
      <link>https://developerjjh.tistory.com/209</link>
      <description>&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;● 순서&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요지&lt;/li&gt;
&lt;li&gt;적용 코드&lt;/li&gt;
&lt;li&gt;마무리&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 요지&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업무를 보면서 특정 데이터를 누구(타 부서 직원)에게 전달해야 하는 일이 생겼다&lt;/li&gt;
&lt;li&gt;단, 이 과정이 매일 반복되고 일정한 시간마다 보내야 되는데, 일일이 데이터를 추출해서 메신저로 보내줬다&lt;/li&gt;
&lt;li&gt;이에 이러한 불편함을 버리고자 단순하게 메일만 보냈던 이메일 전송 라이브러리에서 파일 첨부가 되는지 알아보았다&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 라이브러리 설치&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developerjjh.tistory.com/200&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717309611908&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(Node.js) nodemailer 라이브러리&quot; data-og-description=&quot;어느덧 취직을 해서 업무 하는 와중에 NodeJS를 이용해서 이메일 인증 코드를 전송하는 기능을 만들 수 있다고 한다 이에 저는 'nodemailer' 라는 라이브러리를 통해 이메일 인증코드를 보내서 좀 더 &quot; data-og-host=&quot;developerjjh.tistory.com&quot; data-og-source-url=&quot;https://developerjjh.tistory.com/200&quot; data-og-url=&quot;https://developerjjh.tistory.com/200&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SRw48/hyWg5vYiJa/RCvIdshd1JeBsvLBKkC3RK/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365,https://scrap.kakaocdn.net/dn/c3o4iJ/hyWgWy2z8B/GHXmZZGlHuKjfAVmLUVmI1/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365,https://scrap.kakaocdn.net/dn/cAKMIZ/hyWgVz71BI/zs7oAFOgjd54tMOm3d1Rpk/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerjjh.tistory.com/200&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SRw48/hyWg5vYiJa/RCvIdshd1JeBsvLBKkC3RK/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365,https://scrap.kakaocdn.net/dn/c3o4iJ/hyWgWy2z8B/GHXmZZGlHuKjfAVmLUVmI1/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365,https://scrap.kakaocdn.net/dn/cAKMIZ/hyWgVz71BI/zs7oAFOgjd54tMOm3d1Rpk/img.png?width=782&amp;amp;height=365&amp;amp;face=0_0_782_365');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(Node.js) nodemailer 라이브러리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;어느덧 취직을 해서 업무 하는 와중에 NodeJS를 이용해서 이메일 인증 코드를 전송하는 기능을 만들 수 있다고 한다 이에 저는 'nodemailer' 라는 라이브러리를 통해 이메일 인증코드를 보내서 좀 더&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerjjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 적용 코드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;transport의 sendMail 메서드의 옵션인 &lt;span style=&quot;text-align: start;&quot;&gt;attachments 값을 설정해주면 된다&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;lrm;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1717310268274&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ... 이하 생략 ...

// 보내고자 하는 파일의 값을 가져온다 (buffer형식)
const sendFileData:Buffer = readFileSync(`upload/${파일_이름.확장자}`);

await transporter.sendMail({
      from: process.env.NODEMAILER_USER,
      to: '보내고자 하는 대상의 이메일 주소', // ~~~@gmail.com, ~~~@naver.com 등등
      subject: '이메일 내용',
      html: '이메일 제목',
      attachments: [
        { filename: '첨부파일의 이름', content: sendFileData },
      ],
});
    
// ... 이하 생략 ...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;attachments
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;배열형식으로 첨부하고자 하는 파일의 이름(filename)&lt;/li&gt;
&lt;li&gt;해당 파일의 buffer값(content)을 설정할 수 있다&lt;/li&gt;
&lt;li&gt;객체형식으로 여러개도 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-06-02 154216.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;489&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRsenk/btsHK89W6SE/VXbzumZBEDAG3pKrfCZmzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRsenk/btsHK89W6SE/VXbzumZBEDAG3pKrfCZmzK/img.png&quot; data-alt=&quot;첨부파일 이메일 전송 결과물&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRsenk/btsHK89W6SE/VXbzumZBEDAG3pKrfCZmzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRsenk%2FbtsHK89W6SE%2FVXbzumZBEDAG3pKrfCZmzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;489&quot; data-filename=&quot;스크린샷 2024-06-02 154216.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;489&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;첨부파일 이메일 전송 결과물&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;진작에 알아볼껄 ... 그랬다..ㅋㅋㅋㅋ&lt;/li&gt;
&lt;li&gt;처음에 만들떄는 생각도 안 하고 있다가, 필요하니까 그때 가서야 있나..?라고 찾아보다니&lt;/li&gt;
&lt;li&gt;앞으로도 알아가야할 것들과 공부할 것들은 정말 많다는 걸 느꼈다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Node.js</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/209</guid>
      <comments>https://developerjjh.tistory.com/209#entry209comment</comments>
      <pubDate>Sun, 2 Jun 2024 15:47:36 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) 업로드 이미지 리사이징</title>
      <link>https://developerjjh.tistory.com/208</link>
      <description>&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developerjjh.tistory.com/74&lt;/a&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1717309025166&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(Node.js) multer&quot; data-og-description=&quot;multer 라이브러리 express 서버에서 파일을 업로드하기 위한 미들웨어이다 HTML에서 form데이터를 처리하여 클라이언트에서 서버로 파일을 업로드할 수 있다 multer.diskStorage({}) POST 방식으로 전송된 &quot; data-og-host=&quot;developerjjh.tistory.com&quot; data-og-source-url=&quot;https://developerjjh.tistory.com/74&quot; data-og-url=&quot;https://developerjjh.tistory.com/74&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/f72yL/hyWdme3e1Z/0Bk1alK9KSpotYEVQsWM71/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b0fELv/hyWgXdDTFN/BAiEtL0wAR3xA6InbHo1vk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerjjh.tistory.com/74&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/f72yL/hyWdme3e1Z/0Bk1alK9KSpotYEVQsWM71/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b0fELv/hyWgXdDTFN/BAiEtL0wAR3xA6InbHo1vk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(Node.js) multer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;multer 라이브러리 express 서버에서 파일을 업로드하기 위한 미들웨어이다 HTML에서 form데이터를 처리하여 클라이언트에서 서버로 파일을 업로드할 수 있다 multer.diskStorage({}) POST 방식으로 전송된&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerjjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;● 순서&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;요지&lt;/li&gt;
&lt;li&gt;라이브러리 설치&lt;/li&gt;
&lt;li&gt;sharp 설정 및 적용 코드&lt;/li&gt;
&lt;li&gt;마무리&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 요지&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;multer 라이브러리 통해서 서버에 파일을 업로드할 수 있다&lt;/li&gt;
&lt;li&gt;그중 이미지를 업로드하는 과정에서 만약에...&lt;/li&gt;
&lt;li&gt;특정 유저가 이미지에 대한 크기, 너비, 높이 등 감당할 수 없는 스케일의 사진을 업로드를 한다면...?
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;라이브러리에서의 기본 설정에는 제한이 없지만, &lt;br /&gt;프로젝트의 방향성이나 기획에서 그리고 서버를 구축함에 있어서 메모리를 생각한다면....&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이에 이미지 파일의 크기를 제한하고 제한된 이미지 중 리사이징이 가능하다면 조절해보자..!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 라이브러리 설치&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;shell&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;npm i sharp&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. sharp 적용 코드&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다행히..? import 정도만 해주면 추가적인 설정 없이 리사이징 관련 메서드를 사용하면 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-1. userService.ts&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;// userService.ts
import sharp from 'sharp';

// .. 이하 생략 ..

// 단순하게 multer.uploader를 single로 전제 하였다.
 const { filename } = req.profile;

let selectImg: Buffer;

const uploadImgs = fs.readdirSync(UPLOAD_PATH);

uploadImgs.forEach((imgName) =&amp;gt; {
	if (imgName === filename)
    selectImg = fs.readFileSync(UPLOAD_PATH + filename);
});

// sharp 메서드로 확인하고 하는 이미지의 버퍼값을 넘겨서 metadata에 접근한다
const temp = await sharp(selectImg).metadata();

// 이미지의 너비가 500 보다 크다면 리사이징 한다고 가정해서
// 해당 이미지의 너비를 300으로 설정해준다.
if (temp.width &amp;gt; 500) {
	const tempPath = path.join('upload', `resized-${filename}`);
	await sharp(profile.path) // 바꿀 이미지의 주소
	.resize({ width: 300 }) // 리사이징 하고자 하는 너비
	.withMetadata() // 기존 이미지의 메타데이터를 유지
	.toFile(tempPath); // 리사이징할 이미지 파일을 원하는 주소에 저장

// 리사이징한 이미지를 기존 원본이미지랑 별도로 두지 않고 덮어쓴다.
fs.rename(tempPath, profile.path, (err) =&amp;gt; {
	if (err) throw err;
	});
}
// .. 이하 생략 ..&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3-2. multer.setting&lt;/b&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;typescript&quot;&gt;&lt;code&gt;const uploader = multer({
  storage,
  limits: { fileSize: '원하는 사진의 크기(숫자)' },
  fileFilter: (_, file, cb) =&amp;gt; {
    if (
      file.mimetype !== 'image/png' &amp;amp;&amp;amp;
      file.mimetype !== 'image/jpg' &amp;amp;&amp;amp;
      file.mimetype !== 'image/jpeg'
    ) {
      cb(null, false);
      return cb(new Error('지원되지 않는 이미지 확장자 입니다.'));
    } else cb(null, true);
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업로드를 지원하는 multer 라이브러리의 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 유저가 내 정보 보기에서 본인의 프로필 이미지를 적용할 수 있는 기능을 한다고 했을 때
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;변경하고자 하는 이미지의 크기를 제한할 것인가?&lt;/li&gt;
&lt;li&gt;너비 및 높이 또한 제한할 것인가?&lt;/li&gt;
&lt;li&gt;제한이라고 하면 접근자체가 안되게 할 것인지... 아니면 리사이징을 해줄지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라는 고민을 하고 있다가 같이 일하는 기획자의 방향에 따라 좋은 경험을 할 수 있었다&lt;/li&gt;
&lt;li&gt;시간적 여유가 있었으면 공부하다가 찾아봤을 텐데... 없어서 넘어갈 뻔했지만 다행&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Node.js</category>
      <category>multer</category>
      <category>node</category>
      <category>Sharp</category>
      <category>TS</category>
      <category>리사이징</category>
      <category>이미지</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/208</guid>
      <comments>https://developerjjh.tistory.com/208#entry208comment</comments>
      <pubDate>Sat, 1 Jun 2024 23:51:27 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) 서버에서 차트 그려서 이미지로 변환</title>
      <link>https://developerjjh.tistory.com/207</link>
      <description>&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;● 순서&lt;/span&gt;&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. 요지&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. 기능 설명&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. 라이브러리 설치&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;4. 코드 설명&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;5. 마무리&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. 요지&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 데이터에 대한 차트를 매번 그려서 브라우저에 과부하를 주기보다는&amp;nbsp;가능하다면 서버에서 차트를 그린다&lt;/li&gt;
&lt;li&gt;그린 차트를 이미지로 만들어서 보내줄 수 있는지 없는지 있다면, 그렇게 해준다면 브라우저에 과부하를 막자&lt;/li&gt;
&lt;li&gt;원하는 차트를 빠르게 출력할 수 있지 않을까 라는 ..... 작은... 생각...&lt;/li&gt;
&lt;/ul&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/016.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;몇 번의 검색과 OpenAI에게 물어본 결과로는 가능한 이야기였다&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. 기능 설명&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-02-21 145732.png&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3QWmC/btsE9K4POEK/b5n3Y5hnmJAwKf18n4n8n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3QWmC/btsE9K4POEK/b5n3Y5hnmJAwKf18n4n8n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3QWmC/btsE9K4POEK/b5n3Y5hnmJAwKf18n4n8n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3QWmC%2FbtsE9K4POEK%2Fb5n3Y5hnmJAwKf18n4n8n1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;446&quot; height=&quot;596&quot; data-filename=&quot;스크린샷 2024-02-21 145732.png&quot; data-origin-width=&quot;681&quot; data-origin-height=&quot;910&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;총 2가지의 라이브러리가 도입됐다&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;chart.js : 데이터로 차트를 그리는 라이브러리&lt;/li&gt;
&lt;li&gt;chartjs-node-canvas : chartjs를 노드(서버)에서 그릴 수 있게 도와주는 라이브러리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #1f2328; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차트를 이미지로 변환하기에 애니메이션, 마우스 hover효과 기능이 없다&lt;/li&gt;
&lt;li&gt;이미지의 크기를 정할 수 있다&lt;/li&gt;
&lt;li&gt;차트의 모양을 정할 수 있다&lt;/li&gt;
&lt;li&gt;차트의 라벨지, X, Y축의 라벨지, 범례 등 설정 가능&lt;/li&gt;
&lt;li&gt;차트의 색상 등 CSS효과를 넣을 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;API 작동 순서 요약&lt;/span&gt;&lt;br /&gt;1. 원하는 속성에 대한 값을 요청받는다&lt;br /&gt;1-1. 차트의 모양, 각 라벨지들의 이름, 보일지 말지, 선의 색깔, 내부 색깔, 크기, 확장자 등등&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;2. chartJS로 데이터에 해당하는 녀석들을 종합해서 그린다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;3. 그린 차트를 buffer형식으로 변환한다&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;3-1. chart-node-canvas라이브러리를 통해서 변환&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;4. 변환된 buffer형식의 차트를 원하는 확장자의 이미지로 응답한다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;라고 생각하고 만들어 보려고 한다&lt;/span&gt;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;021&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/021.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/021.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #1f2328; text-align: start;&quot;&gt;결론부터 말하면 조금.. 힘들었던 거 같다... 허허&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;3. 라이브러리 설치&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1708495479168&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i chart.js chartjs-node-canvas&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;4. 코드 설명&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;controller.ts&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708496044083&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const generateLineChart = async (req: Request, res: Response) =&amp;gt; {
  try {
   let chartObj.type = 'line';
	/*
    // 데이터 형식
    chartObj.data = [
      { x: 1, y: 2 },
      { x: 2, y: 3 },
      { x: 4, y: 5 },
    ];
    */
    chartObj.options = setupOptions('line', 0, 45, 0, 50);

    const image = await generateImage(chartObj);

    res
      .writeHead(200, {
        'Content-Type': 'image/png',
        'Content-Length': image.length,
      })
      .end(image);
  } catch (error) {
    console.log(error);
    res.status(500).json(error);
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차트의 모양은 선 그래프를 기준으로 만든다고 가정 (type = 'line')&lt;/li&gt;
&lt;li&gt;차트를 그릴 데이터의 형식은 하나의 객체에 x, y축에 넣을 값을 배열형식으로 정의 (data)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;x축, y축의 데이터 형식이 숫자가 아니어도 된다. 다만,&amp;nbsp; 편의상 숫자로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;option.ts&lt;/p&gt;
&lt;pre id=&quot;code_1708496628688&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ... 생략 ...
switch(chartOption){
	option = {
          plugins: {
            //x,y축에 대한 범례 표시 안함
            legend: { display: false },
            datalabels: { display: false },
          },
          scales: {
            // x축에 대한 설정
            x: {
              // 선형의 형식
              type: 'linear',
              // x축의 수직된 기준선 제거
              grid: { display: false },
              min: 0, // x축에서의 최소값
              max: 45, // x축에서의 최대값
            },
            y: {
              type: 'linear',
              min: 0,
              max: 50,
              // y축에 표시하는 값들의 구간 설정
              ticks: {
                stepSize: 10,
              },
            },
          },
        }
	break;
}      
// ... 생략 ...&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차트에 대한 옵션을 설정 (option)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;차트에 대한 전체적인 제목, 부제목, x&amp;amp;y축에 대한 범례 설정&lt;/li&gt;
&lt;li&gt;각 x, y축 마다의 기준선 표시 여부&lt;/li&gt;
&lt;li&gt;최소/최대 범위&lt;/li&gt;
&lt;li&gt;표시되는 틱마다의 간격&lt;/li&gt;
&lt;li&gt;x,y축 표시 여부&amp;nbsp;&lt;/li&gt;
&lt;li&gt;기타 등등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createImage.ts&lt;/p&gt;
&lt;pre id=&quot;code_1708496728097&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ChartJSNodeCanvas } from 'chartjs-node-canvas';
import { IChart, INodeCanvas } from '../interfaces';

const generateImage = async ({
  type,
  data,
  options,
}: {
  type: IChart['type'];
  data?: IChart['data'];
  options?: IChart['options'];
}): Promise&amp;lt;Buffer&amp;gt; =&amp;gt; {
  let chartJSNodeCanvas: INodeCanvas = new ChartJSNodeCanvas({
    width: 292,
    height: 180,
  });

  const image: Buffer = await chartJSNodeCanvas.renderToBuffer({
    type,
    data,
    options,
  });
  return image;
};

export { generateImage };&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;chartNodeCanvas : 쉽게 말해서 차트의 너비와 높이를 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-16 164321.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PaJA9/btsFaOyUQmd/BrbCmsKtgXvyxDN0W5W7E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PaJA9/btsFaOyUQmd/BrbCmsKtgXvyxDN0W5W7E1/img.png&quot; data-alt=&quot;위 속성들에 따른 그래프 결과 값(데이터 다름주의)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PaJA9/btsFaOyUQmd/BrbCmsKtgXvyxDN0W5W7E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPaJA9%2FbtsFaOyUQmd%2FBrbCmsKtgXvyxDN0W5W7E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;292&quot; height=&quot;104&quot; data-filename=&quot;스크린샷 2024-01-16 164321.png&quot; data-origin-width=&quot;581&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위 속성들에 따른 그래프 결과 값(데이터 다름주의)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;5. 마무리&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;차트(그래프)를 생성하고 그리는 것은 온전히 프론트엔드의 영역인 줄 알았다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;하지만 서버에서도 차트를 생성하여 랜더링 후 이미지 형태로 용이하게 클라이언트에서 사용할 수 있는 경험 할 수 있었던 거 같다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;i&gt;&lt;span style=&quot;color: #000000;&quot;&gt;※ 차트를 생성 후 이미지로 변환하는 API (type 별 다양한 그래프 그려버리기~)&lt;/span&gt;&lt;/i&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1708498598938&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const generateChartToImage = async (req: Request, res: Response) =&amp;gt; {
  try {
    const { type, step, horizon, combo, multi } = req.query;

    const checkType: string = Object.values(CHART_TYPES).find(
      (item) =&amp;gt; item == type,
    );

    // 제공되는 차트의 형식이 아닌 문자열이 넘어 왔을 때 에러반환
    if (!checkType) // ... 생략 ...
    else {
      // 차트 데이터와 그의 속성(옵션), 차트에 대한 전반적인 속성(옵션)들을 담은 객체 선언
      let chartObj: IChartObj = setupChartObject(type.toString());

      let setExtractData: IData[] | number[] =
        horizon || combo || multi
          ? // 조건에 맞는 데이터 형식
          : // 조건에 맞는 데이터 형식2

      // 그래프의 x,y축에 범례의 최소값과 최대값을 구하기 위함
      const { minX, maxX, minY, maxY }: IMinMax =
        calculateMinMaxValues(// 차트 데이터);

      // 그래프 형식에 따른 조건 분류
      switch (type) {
        // 선 그래프 일 경우 꺾은선(No step), 계단식 그래프(step) 분류
        case 'line':
          if (step) {
          	 // ... 생략 ...
          } else {
            // ... 생략 ...
          }
          break;

        // 막대 그래프일 경우 수직, 수평으로 분류
        case 'bar':
          if (horizon) {
            // ... 생략 ...
          } else if (combo) {
            // ... 생략 ...
          } else {
            // ... 생략 ...
          }
          break;

        // pie형식과 도넛형식은 옵션이 같으므로 아래와 같이 설정
        case 'pie':
        case 'doughnut':
          if (multi) {
           // ... 생략 ...
          } else {
            // ... 생략 ...
          }
          break;
        case 'bubble':
          // ... 생략 ...
          break;
        case 'polarArea':
          // ... 생략 ...
          break;
        case 'radar':
         // ... 생략 ...
          break;
        case 'scatter':
          // ... 생략 ...
          break;
      }
      const image = await generateImage(chartObj);
      res
        .writeHead(200, {
          'Content-Type': 'image/png',
          'Content-Length': image.length,
        })
        .end(image);
    }
  } catch (error) {
 	// ... 생략 ...
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqSYtC/btsE976wKY6/dBb47cMKNdC8Xkmbcx0o0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqSYtC/btsE976wKY6/dBb47cMKNdC8Xkmbcx0o0K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;109&quot; data-filename=&quot;edited_스크린샷 2024-01-12 103039.png&quot; data-widthpercent=&quot;71.94&quot; style=&quot;width: 71.1022%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqSYtC/btsE976wKY6/dBb47cMKNdC8Xkmbcx0o0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqSYtC%2FbtsE976wKY6%2FdBb47cMKNdC8Xkmbcx0o0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;109&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c02w3O/btsFbXhNLDp/N4DWaZJB6h8rFx7QxKqEQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c02w3O/btsFbXhNLDp/N4DWaZJB6h8rFx7QxKqEQk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;248&quot; data-origin-height=&quot;231&quot; data-filename=&quot;스크린샷 2024-01-17 165153.png&quot; data-widthpercent=&quot;28.06&quot; style=&quot;width: 27.735%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c02w3O/btsFbXhNLDp/N4DWaZJB6h8rFx7QxKqEQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc02w3O%2FbtsFbXhNLDp%2FN4DWaZJB6h8rFx7QxKqEQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2qqNQ/btsFclvXUlv/kOQNSiYqsc79jru3axdD4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2qqNQ/btsFclvXUlv/kOQNSiYqsc79jru3axdD4K/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;219&quot; data-origin-height=&quot;215&quot; data-filename=&quot;스크린샷 2024-01-16 164407.png&quot; style=&quot;width: 45.3781%; margin-right: 10px; margin-top: 10px;&quot; data-widthpercent=&quot;45.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2qqNQ/btsFclvXUlv/kOQNSiYqsc79jru3axdD4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2qqNQ%2FbtsFclvXUlv%2FkOQNSiYqsc79jru3axdD4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;219&quot; height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1mWNC/btsE8PeiBt8/rUCsPx1MR414sbim0BriT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1mWNC/btsE8PeiBt8/rUCsPx1MR414sbim0BriT1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;250&quot; data-filename=&quot;edited_스크린샷 2024-01-19 153956.png&quot; data-widthpercent=&quot;54.09&quot; style=&quot;width: 53.4591%; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1mWNC/btsE8PeiBt8/rUCsPx1MR414sbim0BriT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1mWNC%2FbtsE8PeiBt8%2FrUCsPx1MR414sbim0BriT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;계단식 선 그래프 / 파이 그래프 / 도넛 그래프 / scatter 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyuAgp/btsE8MIE5r5/AKcaYTEKFQ35HEjUDcxp01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyuAgp/btsE8MIE5r5/AKcaYTEKFQ35HEjUDcxp01/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;265&quot; data-filename=&quot;edited_스크린샷 2024-01-19 154107.png&quot; data-widthpercent=&quot;53.21&quot; style=&quot;width: 52.5867%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyuAgp/btsE8MIE5r5/AKcaYTEKFQ35HEjUDcxp01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyuAgp%2FbtsE8MIE5r5%2FAKcaYTEKFQ35HEjUDcxp01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCnqWL/btsE6RKwH8n/mInOIvSY7YKG9N4Aq4egV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCnqWL/btsE6RKwH8n/mInOIvSY7YKG9N4Aq4egV1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;231&quot; data-filename=&quot;스크린샷 2024-01-19 154153.png&quot; style=&quot;width: 46.2505%;&quot; data-widthpercent=&quot;46.79&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCnqWL/btsE6RKwH8n/mInOIvSY7YKG9N4Aq4egV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCnqWL%2FbtsE6RKwH8n%2FmInOIvSY7YKG9N4Aq4egV1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z1Yz0/btsE7vAkLIQ/WQLZ5GiaNrQkgHF6Nueql1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z1Yz0/btsE7vAkLIQ/WQLZ5GiaNrQkgHF6Nueql1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;232&quot; data-origin-height=&quot;238&quot; data-filename=&quot;스크린샷 2024-01-19 154318.png&quot; data-widthpercent=&quot;27.12&quot; style=&quot;width: 26.4873%; margin-right: 10px; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z1Yz0/btsE7vAkLIQ/WQLZ5GiaNrQkgHF6Nueql1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz1Yz0%2FbtsE7vAkLIQ%2FWQLZ5GiaNrQkgHF6Nueql1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;232&quot; height=&quot;238&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YZjv7/btsE8AascCb/YUS5RSY01N6RXcKqJYdox0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YZjv7/btsE8AascCb/YUS5RSY01N6RXcKqJYdox0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;245&quot; data-filename=&quot;edited_스크린샷 2024-01-19 154228.png&quot; data-widthpercent=&quot;34.06&quot; style=&quot;width: 33.2722%; margin-right: 10px; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YZjv7/btsE8AascCb/YUS5RSY01N6RXcKqJYdox0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYZjv7%2FbtsE8AascCb%2FYUS5RSY01N6RXcKqJYdox0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;245&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9ooeR/btsFaMOAU4K/klJMWRSwJBX754kIrYTwMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9ooeR/btsFaMOAU4K/klJMWRSwJBX754kIrYTwMk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;215&quot; data-filename=&quot;edited_스크린샷 2024-01-19 154353.png&quot; data-widthpercent=&quot;38.82&quot; style=&quot;width: 37.9149%; margin-top: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9ooeR/btsFaMOAU4K/klJMWRSwJBX754kIrYTwMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9ooeR%2FbtsFaMOAU4K%2FklJMWRSwJBX754kIrYTwMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;215&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;레이더 그래프 / 폴라영역 / 버블 그래프 / 콤보(선, 막대) 그래프&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P.S) 이미지 자체로 반환하거나 이미지를 base64 포맷으로 반환할 수도 있던데 하하...&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignCenter&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;022&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/022.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/022.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;pre id=&quot;code_1708499143113&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 방식1
res
      .writeHead(200, {
        'Content-Type': 'image/png',
        'Content-Length': image.length,
      })
      .end(image);
      
// 방식2

res.json({path: `data:image/png;base64,${images.toString('base64')}`})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/207</guid>
      <comments>https://developerjjh.tistory.com/207#entry207comment</comments>
      <pubDate>Sun, 25 Feb 2024 13:06:15 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) node-cron 라이브러리</title>
      <link>https://developerjjh.tistory.com/206</link>
      <description>&lt;h4&gt;● 순서&lt;/h4&gt;
&lt;hr&gt;
&lt;p&gt;1. 라이브러리 설치&lt;/p&gt;
&lt;p&gt;2. node-cron 설정&lt;/p&gt;
&lt;p&gt;3. node-cron 실행 및 적용&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;1. 라이브러리 설치&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm i -D node-cron&lt;/code&gt;으로 라이브러리를 설치해 준다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;npm i -D node-cron&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;&lt;strong&gt;2. node-cron 설정&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;서버가 작동하면서 실행되는 &lt;code&gt;server.ts&lt;/code&gt;라는 최상단 서버파일에 라이브러리를 실행해 준다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import cron from &amp;#39;node-cron&amp;#39;;

cron.schedule(&amp;#39;0 0 * * *&amp;#39;, () =&amp;gt; {
    console.log(&amp;quot;실행&amp;quot;)
});&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;schedule&lt;/code&gt; 메서드를 호출해서 첫 번째 인자에 실행할 주기를 설정할 수 있다&lt;ul&gt;
&lt;li&gt;설정 주기 순 - 초(옵션)/분/시/날짜/월/요일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;현재 기준은 매일 0시 00분에 실행하는 주기로 설정했다&lt;/li&gt;
&lt;li&gt;만약 매초 마다 하고 싶다면 &lt;code&gt;* * * * * *&lt;/code&gt; 처럼 6개를 설정하면 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;3. node-cron 실행&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Ex)&lt;ul&gt;
&lt;li&gt;특정 플랫폼에서 회원탈퇴 기능을 추가&lt;/li&gt;
&lt;li&gt;회원탈퇴 시 90일 동안 다시 회원가입을 할 수 없으며, 매일 일일이 수동으로 확인하고 회원정보를 지울 수 없다&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;node-cron&lt;/code&gt;이라는 라이브러리를 사용해 매일 0시 00분에 90일이 지난 회원정보들을 지워버린다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;strong&gt;3-1. server.ts&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// server.ts
import cron from &amp;#39;node-cron&amp;#39;;

import connectToDB from &amp;#39;./utils/connectToDB&amp;#39;;
import isWithdrawDB from &amp;#39;./utils/withdrawDB&amp;#39;;

/*
    ... 생략 ...
*/

connectToDB(false);

cron.schedule(&amp;#39;0 0 * * *&amp;#39;, () =&amp;gt; isWithdrawDB());

// ... 생략 ...&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;&lt;strong&gt;3-2. withDrawDB&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import { Op } from &amp;#39;sequelize&amp;#39;;

import WithdrawDetail from &amp;#39;../../models/withdrawDetail&amp;#39;;
import User from &amp;#39;../../models/user&amp;#39;;

const KOREA_OFFSET = 9 * 60 * 60 * 1000;

const isWithdrawDB = async () =&amp;gt; {
  try {
    const korNow = new Date(new Date().getTime() + KOREA_OFFSET);

    korNow.setDate(korNow.getDate() - 90);
    const isExpire = await WithdrawDetail.findAll({
      where: {
        updatedAt: {
          [Op.lt]: korNow,
        },
      },
      attributes: [&amp;#39;id&amp;#39;, &amp;#39;userId&amp;#39;],
    });
    if (isExpire.length)
      isExpire.forEach(async (item) =&amp;gt; {
        await WithdrawDetail.destroy({ where: { id: item.id } });
        await User.destroy({ where: { id: item.userId } });
      });
  } catch (error) {
    console.log(error);
  }
};
export default isWithdrawDB;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;탈퇴내역 테이블에서 90일이 지난 데이터가 있으면 해당하는 유저의 정보와 탈퇴정보들을 삭제한다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Node.js</category>
      <category>nodejs</category>
      <category>Schedule</category>
      <category>라이브러리</category>
      <category>특정시간 마다</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/206</guid>
      <comments>https://developerjjh.tistory.com/206#entry206comment</comments>
      <pubDate>Fri, 23 Feb 2024 15:17:00 +0900</pubDate>
    </item>
    <item>
      <title>(DevOps) AWS에서 S3+CodeDeploy로 EC2 자동배포 (2)</title>
      <link>https://developerjjh.tistory.com/205</link>
      <description>&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;● 순서&lt;/span&gt;&lt;/h4&gt;
&lt;hr data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. S3 Bucket 만들기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. AWS CodeDeploy 설정&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. GitHub Action 설정&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;이전 과정은 아래의 게시물에서 확인할 수 있습니다 (AWS IAM 설정 및 EC2 보안설정)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;&lt;a href=&quot;https://developerjjh.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developerjjh.tistory.com/204&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1703481781072&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;(DevOps) AWS에서 S3+CodeDeploy로 EC2 자동배포 (1)&quot; data-og-description=&quot;● 순서 1. AWS IAM 설정 2. EC2 보안설정 1. IAM 설정하기 AWS에서 iam를 검색해서 접속한다 왼쪽 엑세스 관리 - 사용자 배너를 클릭해서 사용자 생성을 누른다 사용자이름 (원하는 이름으로) 작성 후 다&quot; data-og-host=&quot;developerjjh.tistory.com&quot; data-og-source-url=&quot;https://developerjjh.tistory.com/204&quot; data-og-url=&quot;https://developerjjh.tistory.com/204&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/g7ZXK/hyUTD8YeiO/betIWPcvVOUYDBPVbOp9k1/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/HNt5v/hyUPI5axQQ/ORwPNLn0kdGFvIieCwjZDk/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/cQq74Y/hyUPEVYaAW/1NjvwfmSqzwVKDUAFwJ9aK/img.png?width=1405&amp;amp;height=577&amp;amp;face=0_0_1405_577&quot;&gt;&lt;a href=&quot;https://developerjjh.tistory.com/204&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developerjjh.tistory.com/204&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/g7ZXK/hyUTD8YeiO/betIWPcvVOUYDBPVbOp9k1/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/HNt5v/hyUPI5axQQ/ORwPNLn0kdGFvIieCwjZDk/img.png?width=800&amp;amp;height=328&amp;amp;face=0_0_800_328,https://scrap.kakaocdn.net/dn/cQq74Y/hyUPEVYaAW/1NjvwfmSqzwVKDUAFwJ9aK/img.png?width=1405&amp;amp;height=577&amp;amp;face=0_0_1405_577');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;(DevOps) AWS에서 S3+CodeDeploy로 EC2 자동배포 (1)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;● 순서 1. AWS IAM 설정 2. EC2 보안설정 1. IAM 설정하기 AWS에서 iam를 검색해서 접속한다 왼쪽 엑세스 관리 - 사용자 배너를 클릭해서 사용자 생성을 누른다 사용자이름 (원하는 이름으로) 작성 후 다&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developerjjh.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. S3 Bucket 만들기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AWS에서 S3를 검색해서 버킷을 클릭한 후 생성하기를 눌러준다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;s3.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KBtNH/btsCxHDAW9m/MNK1J8k5uqo20BppyU0BH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KBtNH/btsCxHDAW9m/MNK1J8k5uqo20BppyU0BH1/img.png&quot; data-alt=&quot;AWS S3 버킷 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KBtNH/btsCxHDAW9m/MNK1J8k5uqo20BppyU0BH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKBtNH%2FbtsCxHDAW9m%2FMNK1J8k5uqo20BppyU0BH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1748&quot; height=&quot;621&quot; data-filename=&quot;s3.png&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;621&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS S3 버킷 만들기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;만들기 과정에서 버킷 버전관리와 버킷 키에 관한 설정을 모두 비활성화로 선택 후 버킷 만들기를 눌러서 완성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;버킷이름은 전세계적으로 사용하기에 중복될 일이 많으니 유의해야 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;또한, 이름에 대문자를 넣을 수 없으므로 참고해야 한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. AWS CodeDeploy 설정&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AWS에서 CodeDeploy를 검색 후 들어간다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;왼쪽 배너에서 배포 &amp;gt; 애플리케이션 눌러서 애플리케이션 생성 버튼을 눌러 생성한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy.png&quot; data-origin-width=&quot;1395&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhnW1l/btsCCyLVLSY/vOJ5Qky89uMCag2xCWohu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhnW1l/btsCCyLVLSY/vOJ5Qky89uMCag2xCWohu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhnW1l/btsCCyLVLSY/vOJ5Qky89uMCag2xCWohu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhnW1l%2FbtsCCyLVLSY%2FvOJ5Qky89uMCag2xCWohu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1395&quot; height=&quot;323&quot; data-filename=&quot;codedeploy.png&quot; data-origin-width=&quot;1395&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이름은 원하는 대로 편하게 지으면 되고, 컴퓨팅 플랫폼은 EC2/온프레미스로 설정하면 된다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;생성 후 해당 애플리케이션 이름을 눌러서 상세정보로 들어간다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;베포 그룹을 눌러서 배포 그룹 생성을 클릭한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy2.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bASyDR/btsCG5CrX2T/5HFBr6YygDt30rjX3os8R0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bASyDR/btsCG5CrX2T/5HFBr6YygDt30rjX3os8R0/img.png&quot; data-alt=&quot;배포 그룹 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bASyDR/btsCG5CrX2T/5HFBr6YygDt30rjX3os8R0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbASyDR%2FbtsCG5CrX2T%2F5HFBr6YygDt30rjX3os8R0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;545&quot; data-filename=&quot;codedeploy2.png&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배포 그룹 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이름은 마찬가지로 원하는대로 설정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;서비스 역할은 이전 게시물에 설정한 IAM 역할을 추가해 주면 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy3.png&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5AGvh/btsCyocKkHv/rwuyKavFPdvK0pIXshRvi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5AGvh/btsCyocKkHv/rwuyKavFPdvK0pIXshRvi1/img.png&quot; data-alt=&quot;배포그룹 서비스 역할 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5AGvh/btsCyocKkHv/rwuyKavFPdvK0pIXshRvi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5AGvh%2FbtsCyocKkHv%2FrwuyKavFPdvK0pIXshRvi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1022&quot; height=&quot;478&quot; data-filename=&quot;codedeploy3.png&quot; data-origin-width=&quot;1022&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;배포그룹 서비스 역할 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;환경 구성에는 우리가 codeDeploy를 적용할 EC2를 넣어주면 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy4.png&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1lRd/btsCxHDA8RP/bkQVS6bKlMgUZKgGnBfK61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1lRd/btsCxHDA8RP/bkQVS6bKlMgUZKgGnBfK61/img.png&quot; data-alt=&quot;CodeDeploy 환경 구성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1lRd/btsCxHDA8RP/bkQVS6bKlMgUZKgGnBfK61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1lRd%2FbtsCxHDA8RP%2FbkQVS6bKlMgUZKgGnBfK61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1011&quot; height=&quot;628&quot; data-filename=&quot;codedeploy4.png&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CodeDeploy 환경 구성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 EC2내에도 codeDeploy를 실행할 codeDeploy-agent 관련 설치 여부를 안 함으로 해준다&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;i&gt;별도로 설치를 해줄 것이기에&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy5.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1GZIe/btsCyMdmxXc/nWRRVDbaNO2Kf5E6AJKXnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1GZIe/btsCyMdmxXc/nWRRVDbaNO2Kf5E6AJKXnK/img.png&quot; data-alt=&quot;AWS CodeDeploy 에이전트 설치 여부&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1GZIe/btsCyMdmxXc/nWRRVDbaNO2Kf5E6AJKXnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1GZIe%2FbtsCyMdmxXc%2FnWRRVDbaNO2Kf5E6AJKXnK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;441&quot; data-filename=&quot;codedeploy5.png&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS CodeDeploy 에이전트 설치 여부&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;나머지 설정은 아래와 같이 해준다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codedeploy6.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bovBxQ/btsCzDG5Nbc/snqXjxgEJGkrchfRYVZFm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bovBxQ/btsCzDG5Nbc/snqXjxgEJGkrchfRYVZFm0/img.png&quot; data-alt=&quot;나머지 CodeDeploy 배포 그룹 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bovBxQ/btsCzDG5Nbc/snqXjxgEJGkrchfRYVZFm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbovBxQ%2FbtsCzDG5Nbc%2FsnqXjxgEJGkrchfRYVZFm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1052&quot; height=&quot;520&quot; data-filename=&quot;codedeploy6.png&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나머지 CodeDeploy 배포 그룹 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2-1. AWS codeDeploy Agent 설치&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여기까지 설정이 됐으면 우리는 이제 EC2 인스턴스에 codeDeploy 에이전트를 설치를 해줘야 한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;인스턴스를 접속 후 다음과 같이 명령어를 순서대로 입력해서 설치한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703482870564&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get upgrade -y
sudo apt-get install ruby
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위 과정을 순서대로 오류나 실패 없이 완료했다면 'sudo service codedeploy-agent status' 명령어로 확인할 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3. GitHub Action 설정&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;여러 설정들을 해줄 것이다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 우리는 GitHub Action에서 AWS 액세스키와 액세스비밀키를 등록을 해주어서 접근권한을 부여한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;그리고 .yml이라는 파일을 생성하여 우리의 작업 결과물인 build 된 폴더를 S3에 업로드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;업로드된 파일을 codeDeploy를 통해 EC2 인스턴스로 보내준 뒤 해당 파일로 덮어씌워준다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이후에 apache2를 재가동시켜 변동된 파일로 적용해서 배포까지 완료!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-1. 액세스 키, 비밀키 등록&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;action1.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;771&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crXtdE/btsCybYShMq/2JuOCrMumoXVNlQo8k9bq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crXtdE/btsCybYShMq/2JuOCrMumoXVNlQo8k9bq0/img.png&quot; data-alt=&quot;gitHub 환경변수 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crXtdE/btsCybYShMq/2JuOCrMumoXVNlQo8k9bq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrXtdE%2FbtsCybYShMq%2F2JuOCrMumoXVNlQo8k9bq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;674&quot; height=&quot;413&quot; data-filename=&quot;action1.png&quot; data-origin-width=&quot;1258&quot; data-origin-height=&quot;771&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;gitHub 환경변수 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이전 게시물에서 IAM 사용자 설정 후 다운로드하거나 별도 저장 했던 AccessKey와 AccessSecretKey를 넣어준다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; AccessKey의 name : AWS_ACCESS_KEY&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AccessSecretKey의 name : AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;3-2. workflow ci.yml 및 appspec.yml &amp;amp; .sh 파일 생성&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatRight&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;yml.png&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ICBPl/btsCyKT9VuV/Ii59K12hIl39z0UGRyKXf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ICBPl/btsCyKT9VuV/Ii59K12hIl39z0UGRyKXf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ICBPl/btsCyKT9VuV/Ii59K12hIl39z0UGRyKXf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FICBPl%2FbtsCyKT9VuV%2FIi59K12hIl39z0UGRyKXf1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;148&quot; height=&quot;468&quot; data-filename=&quot;yml.png&quot; data-origin-width=&quot;190&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;ci.yml&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;gitHub Action을 작동에 관한 설정 파일이다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;위치는 반드시 .github &amp;gt; workflows 내 위치해야 인식할 수 있다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703483903234&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: portfolio
on:
  push:
    branches:
      - main
jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout source code.
        uses: actions/checkout@v3
      - name: Install dependencies
        run: cd nextjh &amp;amp;&amp;amp; yarn install
      - name: Build
        run: cd nextjh &amp;amp;&amp;amp; yarn run build
      - name: zip distributions
        run: zip -r build.zip ./nextjh/build ./nextjh/appspec.yml ./nextjh/deploy.sh
      - name: Sync Bucket
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_EC2_METADATA_DISABLED: true
        run: aws s3 cp --region ap-northeast-2 ./build.zip s3://jjhportfolio/
      - name: AWS configure credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2
      - name: deploy with AWS codeDeploy
        run: |
          aws deploy create-deployment \
          --application-name portfolio-deploy \
          --deployment-config-name CodeDeployDefault.OneAtATime \
          --deployment-group-name jhfolio \
          --s3-location bucket=jjhportfolio,bundleType=zip,key=build.zip \&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;on : github action에 대한 트리거 설정이다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재는 main이라는 브랜치에 push 했을 때 작동하도록 설정했다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;jobs : action 작동 시 진행할 작업 순서 설정이다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;현재 ubuntu 20.04 버전으로 작동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;steps : 아래의 오는 name별로 하나하나 작동&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;소스코드로 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;의존성 설치&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;프로젝트 빌드&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;빌드파일과 codeDeploy를 실행할 yml과 sh파일을 압축&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AWS 접근 권한 키 인식 후&amp;nbsp;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;S3 Bucket로 압축파일 업로드&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; S3에 있는 파일을 해당하는 codeDeploy 애플리케이션과 배포그룹에 배포시도&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;appspec.yml&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt; CodeDeploy에서 배포를 관리하는 데 사용하는&amp;nbsp; 파일&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;파일에 정의된 일련의 수명 주기 이벤트 훅으로 각 배포를 관리하는 데 사용&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703484458421&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: 0.0
os: linux
files:
  - source: /
    destination: /var/www/
    overwrite: yes

permissions:
  - object: /var/www/
    pattern: &quot;**&quot;
    owner: ubuntu
    group: ubuntu
hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 300
      runas: ubuntu&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;files&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;source : S3에 업로드된 모든 파일들&lt;/li&gt;
&lt;li&gt;destination : EC2 인스턴스에 목적 파일경로&lt;/li&gt;
&lt;li&gt;overwrite : 덮어쓰기를 허용 여부 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;permissions
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;object : 해당 폴더&lt;/li&gt;
&lt;li&gt;pattern : 모든 권한&lt;/li&gt;
&lt;li&gt;owner : EC2 인스턴스 사용자이름&lt;/li&gt;
&lt;li&gt;group : EC2 인스턴스 사용자이름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;hooks
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ApplicatiopnStart : codeDeploy 실행 시 애플리케이션에 연결된 EC2 내부에서 실행할 파일 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;location : 실행할 sh파일의 위치와 파일명 (필수)&lt;/li&gt;
&lt;li&gt;timeout : 최대 실행 시간 (선택)&lt;/li&gt;
&lt;li&gt;runas : EC2 인스턴스 사용자이름 (선택)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;deploy.sh
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;codeDeploy 배포 스크립트 파일&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1703485024075&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash

cd /var/www
sudo cp -r ./build/* ./html
sudo service apache2 restart&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;cd /var/www : appspec.yml의 destination 경로 이동&lt;/li&gt;
&lt;li&gt;sudo cp -r ./build/* ./html : build폴더 내부에 있는 모든 파일들을 ./html폴더안으로 전부 복사 이동&lt;/li&gt;
&lt;li&gt;sudo service apache2 restart : 변경 파일 인식할 수 있도록 apache2 재가동&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;이렇게 한 뒤에 본인의 코드를 git 명령어로 push 하면&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;gitHub Action이 작동하면서 프로젝트 코드를 빌드한 뒤 빌드폴더와 codeDeploy에 사용될 yml파일들을 S3에 업로드&lt;/li&gt;
&lt;li&gt;업로드된 파일이 codeDeploy를 통해 EC2 인스턴스로 이동&lt;/li&gt;
&lt;li&gt;build폴더 내부의 파일들이 apache2가 인식하고 있는 html내부로 복사되어서 덮어쓰어짐&lt;/li&gt;
&lt;li&gt;apache2를 재가동하면 변경된 파일로 적용된 걸 볼 수 있다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 출처)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://senticoding.tistory.com/90&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://senticoding.tistory.com/90&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yoo11052.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yoo11052.tistory.com/113&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blossom6729.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blossom6729.tistory.com/20&lt;/a&gt;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>apache2</category>
      <category>appspec</category>
      <category>Appspec.yml</category>
      <category>aws</category>
      <category>codedeploy</category>
      <category>EC2</category>
      <category>Git</category>
      <category>GitHub</category>
      <category>github action</category>
      <category>yml</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/205</guid>
      <comments>https://developerjjh.tistory.com/205#entry205comment</comments>
      <pubDate>Mon, 25 Dec 2023 15:30:20 +0900</pubDate>
    </item>
    <item>
      <title>(DevOps) AWS에서 S3+CodeDeploy로 EC2 자동배포 (1)</title>
      <link>https://developerjjh.tistory.com/204</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;● 순서&lt;/span&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;1. AWS IAM 설정&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;2. EC2 보안설정&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;1. IAM 설정하기&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;AWS에서 iam를 검색해서 접속한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;왼쪽&amp;nbsp; 액세스 관리 - 사용자 배너를 클릭해서 사용자 생성을 누른다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iam1.png&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2wUfT/btsCxbLzOyf/77QvpsKPkuE6Gru7RhLuW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2wUfT/btsCxbLzOyf/77QvpsKPkuE6Gru7RhLuW1/img.png&quot; data-alt=&quot;AWS IAM 사용자 생성 첫화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2wUfT/btsCxbLzOyf/77QvpsKPkuE6Gru7RhLuW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2wUfT%2FbtsCxbLzOyf%2F77QvpsKPkuE6Gru7RhLuW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1405&quot; height=&quot;577&quot; data-filename=&quot;iam1.png&quot; data-origin-width=&quot;1405&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS IAM 사용자 생성 첫화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;사용자이름 (원하는 이름으로) 작성 후 다음을 클릭한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;권한 옵션은 직접 정책 연결을 누른다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;두 가지를 선택해서 넘어간다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;S3FullAccess&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CodeDeployFullAcess&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iam3.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/It2JB/btsCxaeONXp/ZAON8gM8zJye8w5JZKOh4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/It2JB/btsCxaeONXp/ZAON8gM8zJye8w5JZKOh4K/img.png&quot; data-alt=&quot;AWS IAM 정책 검색(1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/It2JB/btsCxaeONXp/ZAON8gM8zJye8w5JZKOh4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIt2JB%2FbtsCxaeONXp%2FZAON8gM8zJye8w5JZKOh4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1358&quot; height=&quot;322&quot; data-filename=&quot;iam3.png&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS IAM 정책 검색(1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iam2.png&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcXiT6/btsCANI6swK/S1Qvevjo0Khpk6MnAUXkM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcXiT6/btsCANI6swK/S1Qvevjo0Khpk6MnAUXkM1/img.png&quot; data-alt=&quot;AWS IAM 정책 검색(2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcXiT6/btsCANI6swK/S1Qvevjo0Khpk6MnAUXkM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcXiT6%2FbtsCANI6swK%2FS1Qvevjo0Khpk6MnAUXkM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1367&quot; height=&quot;308&quot; data-filename=&quot;iam2.png&quot; data-origin-width=&quot;1367&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS IAM 정책 검색(2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;체크박스만 눌러주고 다음을 눌러서 완성한다&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;해당 사용자 설정에 접근할 수 있는 AcessKey와 AccessSecretKey를 생성해야 한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iam4.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yU24P/btsCxIoLuph/Lm8sUqfPhQxGTxZYFrsRgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yU24P/btsCxIoLuph/Lm8sUqfPhQxGTxZYFrsRgk/img.png&quot; data-alt=&quot;AWS IAM 엑세스 키 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yU24P/btsCxIoLuph/Lm8sUqfPhQxGTxZYFrsRgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyU24P%2FbtsCxIoLuph%2FLm8sUqfPhQxGTxZYFrsRgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;652&quot; height=&quot;138&quot; data-filename=&quot;iam4.png&quot; data-origin-width=&quot;1428&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;AWS IAM 엑세스 키 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;iam5.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0zf8A/btsCw8VGsf4/BkhFX6HykjdxKlIkNCDoU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0zf8A/btsCw8VGsf4/BkhFX6HykjdxKlIkNCDoU0/img.png&quot; data-alt=&quot;엑세스 키 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0zf8A/btsCw8VGsf4/BkhFX6HykjdxKlIkNCDoU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0zf8A%2FbtsCw8VGsf4%2FBkhFX6HykjdxKlIkNCDoU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;361&quot; data-filename=&quot;iam5.png&quot; data-origin-width=&quot;1078&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;엑세스 키 만들기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;생성 후 나오는 AccessKey와 AccessSecretKey를 저장 둬야 한다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;완성 후에만 볼 수 있고 그 이후에는 다시 접근할 수없기에 별도로 저장을 해야 한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;nbsp;왼쪽에 액세스 관리 역할을 클릭 후 역할생성을 클릭한다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;role1.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TJQCw/btsCGaqiZYi/ieKz2BVLJHxYWKMr3FC330/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TJQCw/btsCGaqiZYi/ieKz2BVLJHxYWKMr3FC330/img.png&quot; data-alt=&quot;IAM 역할 생성&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TJQCw/btsCGaqiZYi/ieKz2BVLJHxYWKMr3FC330/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTJQCw%2FbtsCGaqiZYi%2FieKz2BVLJHxYWKMr3FC330%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1786&quot; height=&quot;453&quot; data-filename=&quot;role1.png&quot; data-origin-width=&quot;1786&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM 역할 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;역할생성을 우리는 크게 두 가지를 할 것이다&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;먼저 EC2에 S3와 CodeDeploy에 접근하는 역할&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;CodeDeploy에 관한 역할&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;role2.png&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;730&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcoQ2n/btsCAiijVJn/r6eeFsHx9EhI2OJErv6O7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcoQ2n/btsCAiijVJn/r6eeFsHx9EhI2OJErv6O7K/img.png&quot; data-alt=&quot;IAM EC2 역할 선택(1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcoQ2n/btsCAiijVJn/r6eeFsHx9EhI2OJErv6O7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcoQ2n%2FbtsCAiijVJn%2Fr6eeFsHx9EhI2OJErv6O7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1047&quot; height=&quot;730&quot; data-filename=&quot;role2.png&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;730&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM EC2 역할 선택(1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;role3.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wbvYk/btsCzslnDmN/a2mBEoc4lVFQVJ2sKkZ07K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wbvYk/btsCzslnDmN/a2mBEoc4lVFQVJ2sKkZ07K/img.png&quot; data-alt=&quot;IAM EC2 역할 선택(2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wbvYk/btsCzslnDmN/a2mBEoc4lVFQVJ2sKkZ07K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwbvYk%2FbtsCzslnDmN%2Fa2mBEoc4lVFQVJ2sKkZ07K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1410&quot; height=&quot;311&quot; data-filename=&quot;role3.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IAM EC2 역할 선택(2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;codeDeploy역할은 선택 후 기본설정으로 완료하면 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;role4.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;736&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nfPuC/btsCyvv0Q46/j4n3CtThWXkwKrNKWbv250/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nfPuC/btsCyvv0Q46/j4n3CtThWXkwKrNKWbv250/img.png&quot; data-alt=&quot;CodeDeploy 역할 선택&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nfPuC/btsCyvv0Q46/j4n3CtThWXkwKrNKWbv250/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnfPuC%2FbtsCyvv0Q46%2Fj4n3CtThWXkwKrNKWbv250%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1033&quot; height=&quot;736&quot; data-filename=&quot;role4.png&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;736&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CodeDeploy 역할 선택&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;2. EC2 보안설정&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;이제 사용할 EC2 인스턴스로 넘어가서 보안설정을 추가 및 수정해 주면 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ec2_iam.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n2KGM/btsCzs6KO5q/YGnLQTtGcEzTZqL4w05oM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n2KGM/btsCzs6KO5q/YGnLQTtGcEzTZqL4w05oM0/img.png&quot; data-alt=&quot;EC2 인스턴스 보안 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n2KGM/btsCzs6KO5q/YGnLQTtGcEzTZqL4w05oM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn2KGM%2FbtsCzs6KO5q%2FYGnLQTtGcEzTZqL4w05oM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;341&quot; data-filename=&quot;ec2_iam.png&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;EC2 인스턴스 보안 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;IAM 역할 수정을 클릭 후 위 과정에서 생성한 IAM 설정으로 업데이트해주면 된다&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>DevOps</category>
      <category>aws</category>
      <category>AWS IAM</category>
      <category>CD</category>
      <category>CI</category>
      <category>CI/CD</category>
      <category>codedeploy</category>
      <category>EC2</category>
      <category>iam</category>
      <category>Instance</category>
      <category>인스턴스</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/204</guid>
      <comments>https://developerjjh.tistory.com/204#entry204comment</comments>
      <pubDate>Mon, 25 Dec 2023 14:20:51 +0900</pubDate>
    </item>
    <item>
      <title>(Node.js) MVC 디자인 패턴</title>
      <link>https://developerjjh.tistory.com/203</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;MVC 패턴 NodeJS에 적용해 보기!!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;0. MVC패턴을 적용하게 된 계기&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. MVC패턴이란&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. MVC 패턴을 선택한 이유&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. MVC 패턴에 대한 간단한 설명&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. 나의 MVC패턴 적용 전 / 후 Server Tree&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. 후기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;0. 잊고 있던 디자인 패턴 ( MVC 패턴)&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 작성을 시작하기 전에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴... 이전에 국비교육을 다닐 때 지나가는 듯이 들었던 기억이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리곤 나중에 제대로 알아봐야겠다라고 생각하고 잊어버렸던 거 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 어느 날...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친한 N사 백엔드 개발자를 만나면서 궁금해하길래 내 gitHub를 보여줬다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 녀석이 보자마자 하는 말은...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;뭐야 형... 이거 패턴이 왜 이래?&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이야기를 듣고 나선 아차 싶으면서 잊었던 디자인패턴이라는 것이 생각났다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 다시 디자인패턴을 알아봐야겠다라고 생각 했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에서도 MVC패턴을 찾아서 적용해보려고 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;1. MVC패턴이란 ?&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;example.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x7MEd/btsvlz51SSV/HOz4LUbomVoO2EK7Q5PFrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x7MEd/btsvlz51SSV/HOz4LUbomVoO2EK7Q5PFrk/img.png&quot; data-alt=&quot;MVC 패턴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x7MEd/btsvlz51SSV/HOz4LUbomVoO2EK7Q5PFrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx7MEd%2Fbtsvlz51SSV%2FHOz4LUbomVoO2EK7Q5PFrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;397&quot; data-filename=&quot;example.png&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MVC 패턴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span data-sheets-value=&quot;{&amp;quot;1&amp;quot;:2,&amp;quot;2&amp;quot;:&amp;quot;어플리케이션을 구성할 때 각자 맡은 역할대로 소스코드를 구분하여 코딩하고 유지보수 및 신규기능 추가를 더 수월하게 하겠다는 의도&amp;quot;}&quot; data-sheets-userformat=&quot;{&amp;quot;2&amp;quot;:769,&amp;quot;3&amp;quot;:{&amp;quot;1&amp;quot;:0},&amp;quot;11&amp;quot;:4,&amp;quot;12&amp;quot;:0}&quot;&gt;2. MVC 패턴 선점이유&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애플리케이션을 구성할 때 각자 맡은 역할대로 소스코드를 구분하여 코딩하고 유지보수 및 신규기능 추가를 더 수월하게 하겠다는 의도&lt;/li&gt;
&lt;li&gt;Java에서 매우 많이 쓰이는 패턴으로 같이 작업하는 백엔드 인원들이 다가오기 수월한 패턴이라고 생각해서 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;3. MVC 패턴에 대한 설명&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Model
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;데이터를 관리하고 처리, &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;데이터베이스, 처음의 정의하는 상수, 초기화값, 변수&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;MySQL로 Sequelize를 사용해서 ORM 하여 DB를 구성&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;View
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;사용자에게 보이는 인터페이스를 제공&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Controller
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;사용자의 요청을 처리하고 Model과 View를 연결&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;기능을 수행하는 함수들 및 로직 폴더, 대부분의 요청에 대한 처리, 함수 선언을 한 곳에 모아둔 폴더&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;데이터와 사용자인터페이스 요소들을 잇는 다리역할&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;사용자가 데이터를 클릭하고, 수정하는 것에 대한 &quot;이벤트&quot;들을 처리하는 부분&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;4. 나의 MVC 패턴 적용하기 전 / 후 Server Tree&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;MVC 패턴을 적용하기 전&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;나에게는 익숙했지만... 일을 같이 하는 개발자들이 다가가기 어려웠다고 이야기를 했었다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;그래서 고민고민하다가 Java Spring에서 많이 쓰인다고 하는 MVC패턴을 적용했다&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;4-1. 적용 전 백엔드 서버 Tree&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─config&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;├─build&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─models&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─src&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;└─&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;upload&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.env&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.gitignore&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.prettierrc&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;package.json&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;README.md&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;index.ts&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;tsconfig.json&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;before_Tree.jpg&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgZ6IC/btsvkQNKbHk/5p2FXkGH7kpEkGuAAWFAb0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgZ6IC/btsvkQNKbHk/5p2FXkGH7kpEkGuAAWFAb0/img.jpg&quot; data-alt=&quot;before_Tree&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgZ6IC/btsvkQNKbHk/5p2FXkGH7kpEkGuAAWFAb0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgZ6IC%2FbtsvkQNKbHk%2F5p2FXkGH7kpEkGuAAWFAb0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;295&quot; height=&quot;682&quot; data-filename=&quot;before_Tree.jpg&quot; data-origin-width=&quot;295&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;before_Tree&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;이런 식으로 그냥.. config폴더에 DB 설정파일 두고 models에는 DB 테이블 관련 파일들...&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외 소스파일들은 src 폴더에 싹 다 집어넣고 사용했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 당시에는 몰랐지만, 같이 이방식으로 공부한 게&amp;nbsp; 아니라면 다들 어려워할 수 있다는 걸 알아챘다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;4-2. 적용 후 백엔드 서버 Tree&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─config&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─controllers&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─models&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─routes&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;├─upload&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;└─utils&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.env&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.gitignore&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;.prettierrc&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;package.json&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;README.md&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;server.ts&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #000000;&quot;&gt;tsconfig.json&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;after_Tree.jpg&quot; data-origin-width=&quot;183&quot; data-origin-height=&quot;711&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddpdIg/btsvje2PYaY/KomzjqfIKS4uOhR9LHhjZ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddpdIg/btsvje2PYaY/KomzjqfIKS4uOhR9LHhjZ0/img.jpg&quot; data-alt=&quot;after MVC 패턴 적용 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddpdIg/btsvje2PYaY/KomzjqfIKS4uOhR9LHhjZ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddpdIg%2Fbtsvje2PYaY%2FKomzjqfIKS4uOhR9LHhjZ0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;183&quot; height=&quot;711&quot; data-filename=&quot;after_Tree.jpg&quot; data-origin-width=&quot;183&quot; data-origin-height=&quot;711&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;after MVC 패턴 적용 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc; color: #000000; text-align: start;&quot; data-line=&quot;159&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;159&quot;&gt;&lt;b&gt;config&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 서버와 DB가 시작할 때 처리되는 파일들을 모아둔 폴더&lt;/li&gt;
&lt;li data-line=&quot;159&quot;&gt;&lt;b&gt;controllers&lt;/b&gt; : 기능에 따른 관리 파일들을 모아둔 폴더&lt;/li&gt;
&lt;li data-line=&quot;159&quot;&gt;&lt;b&gt;models&lt;/b&gt; : DB의 테이블명과 해당 컬럼들의 대한 속성 파일들을 모아둔 폴더&lt;/li&gt;
&lt;li data-line=&quot;160&quot;&gt;&lt;b&gt;routes&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 사용자의 요청에 따른 API url 분산해둔 폴더&lt;/li&gt;
&lt;li data-line=&quot;161&quot;&gt;&lt;b&gt;upload&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 업로드된 파일을 저장해 두는 폴더&lt;/li&gt;
&lt;li data-line=&quot;162&quot;&gt;&lt;b&gt;utils&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 기타 자주 쓰이거나 공통으로 사용하는 모듈, 설정 값들을 관리 (interface, multer 등등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;5. MVC 패턴 적용 후기&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 기능에 대한 controller를 나눠서 쪼개놨더니 기존에 Java를 배우던 개발자들이 보기 쉬워졌다고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그제야 이야기해 줬다 ㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 불편한 거 있으면 이야기해 줘.... 난 몰랐단 말이야...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폴더 내 파일이름도 기능+Controller로 하자고 해서 적용했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 API 라우터 내에 실행되는 기능들도 네이밍을 해서 함수화 하여서 controller에 분리해두니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업하는 과정에서 다들 이전보다 편하다고 이야기해 줬다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 지금이 상태도 MVC를 적용하고 직후의 상태는 아니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속해서 바꾸고 바꾸고 공통적으로 사용하거나 각 파일별 interface파일은 별도로 존재했다가 하나로 합치는 등&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누군가 이런 식으로 하면 어때? 혹은 변화를 주고 회의를 진행함으로써 조금이라도 편하고 보편화된 방법을 수용하려고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노력 중에 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;P.S 뭐.. ㅋㅋㅋ 많은 분들이 보고 있지는 않지만 누군가 보고 좀 더 괜찮은 방법이 있다면 따끔하게 알려주세요!!!&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다 !!&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;friends2&quot; data-emoticon-name=&quot;041&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/041.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends2/large/041.png&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;</description>
      <category>Node.js</category>
      <category>DESIGN</category>
      <category>design pattern</category>
      <category>Java</category>
      <category>JS</category>
      <category>MVC</category>
      <category>node</category>
      <category>nodejs</category>
      <category>Pattern</category>
      <category>Spring</category>
      <author>JJeongHyun</author>
      <guid isPermaLink="true">https://developerjjh.tistory.com/203</guid>
      <comments>https://developerjjh.tistory.com/203#entry203comment</comments>
      <pubDate>Sun, 24 Sep 2023 16:46:21 +0900</pubDate>
    </item>
  </channel>
</rss>