๐ [NestJS] PM2 ์๊ฐ๋ถํฐ Docker + Github Action์ ํ์ฉํ ๋ฐฐํฌ๊น์ง
์ผ๋ฐ์ ์ผ๋ก NestJS ํน์ NextJS๋ Node.js์ ๋ฐํ์ ์์์ ๋์ํ๊ธฐ ๋๋ฌธ์ ์ฑ๊ธ ์ค๋ ๋ ๊ธฐ๋ฐ์ผ๋ก ์๋ํฉ๋๋ค.
์ด๋ฌํ ๊ตฌ์กฐ๋ ๋ง์ ์์ฒญ์ด ๋ค์ด์ฌ ๊ฒฝ์ฐ ๋ณ๋ชฉํ์์ด ๋ฐ์ํด ์ด๋ฒคํธ ๋ฃจํ๊ฐ ์ฐจ๋จ๋์ด ์๋ต์ฑ์ด ์ ํ๋ฉ๋๋ค.
์ด๋ด ๋ ์ฌ๋ฌ ํ๋ก์ธ์ค๋ฅผ ์์ฑํ์ฌ ๋ฉํฐ ์ฝ์ด๋ฅผ ํ์ฉํ๊ธฐ ์ํด ์ฃผ๋ก ์ฌ์ฉํ๋ ํ๋ก์ธ์ค ๋งค๋์ ๊ฐ ์๋๋ฐ์, ๋ฐ๋ก PM2์ ๋๋ค.
๐ PM2๋
PM2๋ Node.js ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ด๋ฆฌํ๊ธฐ ์ํ ํ๋ก๋์ ํ๋ก์ธ์ค ๋งค๋์ ์ ๋๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์ ์คํ, ๋ชจ๋ํฐ๋ง, ๋ก๋ ๋ฐธ๋ฐ์ฑ, ๋ฌด์ค๋จ ๋ฐฐํฌ ๋ฑ์ ์ง์ํ์ฌ, Node.js ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ ์ ์ผ๋ก ์ด์ํ ์ ์๊ฒ ๋์์ค๋๋ค.
ํนํ, ์๋ฒ์์ Node.js ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๊ณ ๊ด๋ฆฌํ ๋ ํ์ํ ์ฌ๋ฌ ๊ธฐ๋ฅ์ ํตํฉ์ ์ผ๋ก ์ ๊ณตํ์ฌ ๊ฐ๋ฐ์์ ์ด์์์ ํธ์์ฑ์ ๋์ ๋๋ค.
๐ ์ฅ์
์ฃผ์ํ ํน์ฅ์ ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ๋ก์ธ์ค ๊ด๋ฆฌ
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์์, ์ค์ง, ์ฌ์์, ์ญ์ ๋ฑ์ ์์ ์ ๋ช ๋ น์ด๋ก ๊ฐ๋จํ ์ํํ ์ ์์ต๋๋ค.
- ํ๋ก์ธ์ค ํฌ๋์ ์ ์๋ ์ฌ์์์ ์ง์ํ์ฌ ์๋น์ค ์์ ์ฑ์ ๋ณด์ฅํฉ๋๋ค.
- ํด๋ฌ์คํฐ ๋ชจ๋ ์ง์
- ๋ฉํฐ ์ฝ์ด ์๋ฒ์์ ํด๋ฌ์คํฐ ๋ชจ๋๋ก ์คํํ๋ฉด CPU๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์ต๋๋ค.
- ๋จ์ผ ํ๋ก์ธ์ค์ ํ๊ณ๋ฅผ ๋์ด์ ๋ ๋ง์ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- ๋ฌด์ค๋จ ๋ฐฐํฌ (Zero Downtime Reload)
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ์์ํ์ง ์๊ณ ๋ ์ ์ฝ๋๋ฅผ ์ ์ฉํ ์ ์์ต๋๋ค.
- pm2 reload ๋ช ๋ น์ผ๋ก ์๋น์ค ์ค๋จ ์์ด ์ ๋ฐ์ดํธ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
- ๋ก๊ทธ ๊ด๋ฆฌ ๋ฐ ๋ชจ๋ํฐ๋ง
- ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํ ๋ก๊ทธ(ํ์ค ์ถ๋ ฅ ๋ฐ ์๋ฌ)๋ฅผ ์๋์ผ๋ก ์ ์ฅํ๊ณ ๊ด๋ฆฌํฉ๋๋ค.
- ์ค์๊ฐ์ผ๋ก ๋ก๊ทธ๋ฅผ ํ์ธํ๊ฑฐ๋(pm2 logs), ๋ก๊ทธ ํ์ผ์ ์กฐํํ ์ ์์ต๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ์ํ๋ฅผ ๋ชจ๋ํฐ๋งํ๋ CLI ๋๋ ์น ๋์๋ณด๋๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ํ๊ฒฝ ๋ณ์ ๊ด๋ฆฌ
- JSON ๋๋ ecosystem.config.js ํ์ผ๋ก ํ๊ฒฝ ๋ณ์๋ฅผ ์ ์ํ๊ณ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
- ๋ค์ํ ์คํ ํ๊ฒฝ(dev, staging, production)์ ์ฝ๊ฒ ์ค์ ํ ์ ์์ต๋๋ค.
- ์ ํ๋ฆฌ์ผ์ด์
์ํ ๋ชจ๋ํฐ๋ง
- pm2 monit ๋ช ๋ น์ผ๋ก CPU, ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋, ์คํ ์ํ ๋ฑ์ ํ์ธํ ์ ์์ต๋๋ค.
- PM2 Plus์ ๊ฐ์ ์ถ๊ฐ ์๋น์ค๋ฅผ ํตํด ๋ ์์ธํ ๋ชจ๋ํฐ๋ง๊ณผ ์๋ฆผ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ๋ฐฐํฌ ์๋ํ
- pm2 deploy ๋ช ๋ น์ ์ฌ์ฉํด ์ ํ๋ฆฌ์ผ์ด์ ์ ์๊ฒฉ ์๋ฒ์ ์ฝ๊ฒ ๋ฐฐํฌํ ์ ์์ต๋๋ค.
- Git ๋ฆฌํฌ์งํ ๋ฆฌ์ ์ฐ๋ํ์ฌ ์๋์ผ๋ก ์ฝ๋๋ฅผ ๊ฐ์ ธ์ ๋ฐฐํฌํ๋ ์ํฌํ๋ก๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
node ์ํ๊ณ์ ๋๋ฌด๋ ํ์ํ ๊ธฐ๋ฅ๋ค์ ๊ฝ๊ฝ ์ฑ์ ๋ฃ์ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๐ ์ฃผ์ ๋ช ๋ น์ด
pm2๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ์ฌ์ ์ ์ค์น๊ฐ ํ์ํฉ๋๋ค.
$ npm i -g pm2
- ์ฑ ์คํ
$ pm2 start app.js --name my-app
- ํ๊ฒฝ ํ์ผ์ ์ฌ์ฉํ ์ฑ ์คํ
$ pm2 start ecosystem.config.js

- ์ฑ ์ค์ง
$ pm2 stop my-app
$ pm2 delete my-app
- pm2 ์ข ๋ฃ
$ pm2 kill

- ์ค์๊ฐ ๋ชจ๋ํฐ๋ง
$ pm2 monit

- ์ค์๊ฐ ๋ก๊ทธ ํ์ธ
$ pm2 logs
๐ ํ๊ฒฝ ํ์ผ (ecosystem.config.js)
PM2 ํ๊ฒฝ ํ์ผ์ ์ผ๋ฐ์ ์ผ๋ก ecosystem.config.js ๋๋ ecosystem.json ์ด๋ฆ์ผ๋ก ์์ฑํ๋ฉฐ ํ๋ก์ ํธ ๋ฃจํธ์ ์์นํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
module.exports = {
apps: [
{
// ์ ํ๋ฆฌ์ผ์ด์
์ด๋ฆ (PM2์์ ์ฌ์ฉํ ์ด๋ฆ)
// PM2 ๋ช
๋ น์ด์์ ์ด ์ด๋ฆ์ ์ฌ์ฉํ์ฌ ์ ํ๋ฆฌ์ผ์ด์
์ ์ ์ดํฉ๋๋ค.
name: "my-app",
// ์คํํ ์คํฌ๋ฆฝํธ ํ์ผ ๊ฒฝ๋ก
// Node.js ํ์ผ๋ฟ๋ง ์๋๋ผ, Bash, Python ๋ฑ ๋ค๋ฅธ ์คํฌ๋ฆฝํธ๋ ์คํ ๊ฐ๋ฅํฉ๋๋ค.
script: "./dist/main.js",
// ํ์ฌ ์์
๋๋ ํ ๋ฆฌ (๊ธฐ๋ณธ๊ฐ: ํ์ฌ ๋๋ ํ ๋ฆฌ)
// ์ ํ๋ฆฌ์ผ์ด์
์คํ ๊ธฐ์ค์ด ๋๋ ๊ฒฝ๋ก๋ฅผ ์ง์ ํฉ๋๋ค.
cwd: "/home/user/my-app",
// ์คํ ๋ชจ๋: "fork" ๋๋ "cluster"
// fork: ๋จ์ผ ํ๋ก์ธ์ค ์คํ
// cluster: ๋ฉํฐ ์ฝ์ด๋ฅผ ํ์ฉํ์ฌ ์ฌ๋ฌ ํ๋ก์ธ์ค ์คํ
exec_mode: "cluster",
// ์คํํ ์ธ์คํด์ค ์
// cluster ๋ชจ๋์์๋ง ์ ํจํ๋ฉฐ, "max"๋ก ์ค์ ํ๋ฉด ๋ชจ๋ CPU ์ฝ์ด๋ฅผ ์ฌ์ฉํฉ๋๋ค.
instances: "max",
// ์ ํ๋ฆฌ์ผ์ด์
์ ์ ๋ฌํ ์ธ์
// ์คํ ์ ์ปค๋งจ๋๋ผ์ธ ์ธ์๋ก ์ ๋ฌ๋ฉ๋๋ค.
args: ["--port", "3000"],
// ๊ธฐ๋ณธ ํ๊ฒฝ ๋ณ์ ์ค์
// NODE_ENV, PORT ๋ฑ ์คํ ํ๊ฒฝ์ ์ ์ํฉ๋๋ค.
env: {
NODE_ENV: "development", // ๊ฐ๋ฐ ํ๊ฒฝ
PORT: 3000 // ๊ธฐ๋ณธ ํฌํธ
},
// ํ๋ก๋์
ํ๊ฒฝ์์ ์ฌ์ฉํ ํ๊ฒฝ ๋ณ์ ์ค์
// "pm2 start ecosystem.config.js --env production"์ผ๋ก ์คํ ์ ์ ์ฉ๋ฉ๋๋ค.
env_production: {
NODE_ENV: "production", // ํ๋ก๋์
ํ๊ฒฝ
PORT: 8000 // ํ๋ก๋์
ํฌํธ
},
// ๋ก๊ทธ ํ์ผ ๊ฒฝ๋ก ์ค์ (ํ์ค ์ถ๋ ฅ๊ณผ ์๋ฌ ๋ก๊ทธ ํตํฉ)
log_file: "/var/logs/my-app-combined.log",
// ํ์ค ์ถ๋ ฅ ๋ก๊ทธ ํ์ผ ๊ฒฝ๋ก
out_file: "/var/logs/my-app-out.log",
// ์๋ฌ ๋ก๊ทธ ํ์ผ ๊ฒฝ๋ก
error_file: "/var/logs/my-app-error.log",
// ํ์ผ ๋ณ๊ฒฝ ๊ฐ์ง (๊ธฐ๋ณธ๊ฐ: false)
// true๋ก ์ค์ ํ๋ฉด ํ์ผ ๋ณ๊ฒฝ ์ ์ ํ๋ฆฌ์ผ์ด์
์ ์๋์ผ๋ก ์ฌ์์ํฉ๋๋ค.
watch: true,
// ๊ฐ์์์ ์ ์ธํ ํ์ผ/ํด๋ (watch๊ฐ true์ผ ๋ ์ ํจ)
ignore_watch: ["node_modules", "logs"],
// ์๋ ์ฌ์์ ์ฌ๋ถ (๊ธฐ๋ณธ๊ฐ: true)
// false๋ก ์ค์ ํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์
์ด ์ข
๋ฃ๋ ์ํ๋ก ์ ์ง๋ฉ๋๋ค.
autorestart: true,
// ์ฌ์์ ์๋ ํ์ (๊ธฐ๋ณธ๊ฐ: ๋ฌด์ ํ)
// ํน์ ํ์ ์ด์ ์คํจํ๋ฉด ํ๋ก์ธ์ค๋ฅผ ์ข
๋ฃํฉ๋๋ค.
max_restarts: 10,
// ์ต๋ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ (๊ธฐ๋ณธ๊ฐ: ์ ํ ์์)
// ํ๋ก์ธ์ค๊ฐ ์ด ๋ฉ๋ชจ๋ฆฌ ์ ํ์ ์ด๊ณผํ๋ฉด ์ฌ์์ํฉ๋๋ค.
max_memory_restart: "300M",
// ๊ฐ๋ฐ ์ค ๋๋ฒ๊น
์ ์ํ ์์ธ ๋ก๊ทธ ํ์ฑํ (๊ธฐ๋ณธ๊ฐ: false)
// true๋ก ์ค์ ํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์
๋๋ฒ๊ทธ ๋ก๊ทธ๋ฅผ ๋ ๋ง์ด ์ถ๋ ฅํฉ๋๋ค.
debug: false,
// ์คํ ์ค์ธ ํ๋ก์ธ์ค์ CPU/๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ํ์ธํ ์ฃผ๊ธฐ (๊ธฐ๋ณธ๊ฐ: 10์ด)
// PM2 Dashboard์ ๊ฐ์ ๋ชจ๋ํฐ๋ง ๋๊ตฌ์์ ์ฌ์ฉ๋ฉ๋๋ค.
min_uptime: "1m", // ์ต์ ์คํ ์๊ฐ (ํ๋ก์ธ์ค๊ฐ ์ด ์๊ฐ ์ด์ ์ ์ข
๋ฃ๋๋ฉด ๋น์ ์์ผ๋ก ๊ฐ์ฃผ)
max_uptime: "24h", // ์ต๋ ์คํ ์๊ฐ (์ด ์๊ฐ์ด ์ง๋๋ฉด ํ๋ก์ธ์ค๋ฅผ ์ฌ์์)
// ์ฌ์ฉ์ ์ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ ํ๋ฆฌ์ผ์ด์
์คํ ํ ์คํ
post_start_script: "./scripts/postStart.sh"
}
]
};
๐ Github Action + Docker + PM2๋ก NestJS๋ฐฐํฌํ๊ธฐ
์๋ฌด๋๋ ์์ฆ์ ๋ฐฐํฌํ ๋ ๋์ปค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ ํ์ด ์๋ ํ์๊ฐ ๋์ด๋ฒ๋ ธ์ต๋๋ค.
์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ๋ฐฐํฌํ ๋ pm2์ ๋์ปค๋ฅผ ์ฌ์ฉํ ๊ฒ์ ์์๋ก ๋ค์ด ์๊ฐ๋๋ฆฝ๋๋ค.
๐ ecosystem.config.js
module.exports = {
apps: [
{
name: 'franchise-backend',
script: './dist/main.js',
exec_mode: 'cluster',
instances: '2', // CPU ์ฝ์ด ์
max_memory_restart: '1000M', // ํ๋ก์ธ์ค์ ๋ฉ๋ชจ๋ฆฌ๊ฐ 1000MB์ ๋๋ฌํ๋ฉด reload ์คํ
merge_logs: true, // ํด๋ฌ์คํฐ ๋ชจ๋ ์ฌ์ฉ ์ ๊ฐ ํด๋ฌ์คํฐ์์ ์์ฑ๋๋ ๋ก๊ทธ๋ฅผ ํ ํ์ผ๋ก ํฉ์นจ
autorestart: true, // ํ๋ก์ธ์ค ์คํจ ์ ์๋์ผ๋ก ์ฌ์์ํ ์ง ์ ํ
env_production: {
NODE_ENV: 'production',
PORT: 3000,
DATABASE_URL: 'VAL_DATABASE_URL',
OPENAPI_KEY: 'VAL_OPENAPI_KEY',
DEFAULT_YEAR: 'VAL_DEFAULT_YEAR',
},
},
],
};
๋จผ์ ๋ฐฑ์ค๋ ์ฑ์ ์ด๋ป๊ฒ ์คํํ ์ง pm2 ์ค์ ํ์ผ์ ์์ฑํฉ๋๋ค.
ํ์ํ ํ๊ฒฝ ๋ณ์๊ฐ ์๋ค๋ฉด env_production ๋ธ๋ญ ์์ ์ถ๊ฐ์ ์ผ๋ก ์์ฑํด ์ค๋๋ค.
์ ๋ ์ฒ๋ฆฌ๋์ด ํฌ๊ฒ ๋ง์ ๊ฒ ๊ฐ์ง ์์ ์ฐ์ ์ฝ์ด๋ฅผ 2๊ฐ ์ ๋๋ง ์ฌ์ฉํ๋๋ก ํ์ต๋๋ค.
๐ Dockerfile
FROM node:22-alpine AS base
FROM base AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm
RUN pnpm i --frozen-lockfile
COPY . .
ARG VAL_DATABASE_URL
ARG VAL_OPENAPI_KEY
ARG VAL_DEFAULT_YEAR
RUN sed -i "s|VAL_DATABASE_URL|$VAL_DATABASE_URL|" ecosystem.config.js
RUN sed -i "s|VAL_OPENAPI_KEY|$VAL_OPENAPI_KEY|" ecosystem.config.js
RUN sed -i "s|VAL_DEFAULT_YEAR|$VAL_DEFAULT_YEAR|" ecosystem.config.js
RUN pnpm build
FROM base AS runner
WORKDIR /app
RUN npm install -g pm2
COPY --from=builder /app .
RUN npm run prisma:generate
EXPOSE 3000
CMD ["pm2-runtime", "start", "ecosystem.config.js", "--env", "production"]
pnpm ํจํค์ง ๋งค๋์ ๋ฅผ ์ด์ฉํ ๋น๋ ๋ฐฉ๋ฒ์ ๋๋ค.
- sed ๋ช ๋ น์ด๋ก ๋ฐฐํฌ ์คํฌ๋ฆฝํธ์์ ์ฃผ์ ๋ฐ์ ํ๊ฒฝ๋ณ์๋ฅผ ecosystem์ ์นํํด ์ฃผ์์ต๋๋ค.
- ์คํ ๋ถ๋ถ์์ pm2๋ฅผ ์ค์นํด ์ฃผ์์ต๋๋ค.
pm2 start๊ฐ ์๋ ์ปจํ ์ด๋ ํ๊ฒฝ์ ์ต์ ํ๋์ด
๋ฐ๋ชฌ ์์ด ํ๋ก์ธ์ค๋ฅผ ์คํํ๋pm2-runtime start๋ช ๋ น์ด๋ฅผ ์ด์ฉํด ์คํํฉ๋๋ค.
๐ Github Action Workflow
name: deploy
on:
push:
branches: [main]
jobs:
app-build:
runs-on: ubuntu-latest
env:
DOCKER_IMAGE_TAG: ${{ secrets.DOCKER_REPOSITORY }}/${{secrets.DOCKER_IMAGENAME}}:latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login docker hub
uses: docker/login-action@v3
with:
registry: ${{ secrets.DOCKER_REPOSITORY }}
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
push: true
tags: ${{ env.DOCKER_IMAGE_TAG }}
build-args: |
VAL_DATABASE_URL=${{secrets.VAL_DATABASE_URL}}
VAL_OPENAPI_KEY=${{secrets.VAL_OPENAPI_KEY}}
VAL_DEFAULT_YEAR=${{secrets.VAL_DEFAULT_YEAR}}
- name: execute remote ssh
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USERNAME }}
port: ${{ secrets.SERVER_PORT }}
key: ${{ secrets.SERVER_KEY }}
script: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker pull ${{ env.DOCKER_IMAGE_TAG }}
docker stop ${{secrets.DOCKER_IMAGENAME}} || true
docker rm ${{secrets.DOCKER_IMAGENAME}} || true
docker run -d --restart=always --name ${{secrets.DOCKER_IMAGENAME}} -p 3001:3000 ${{ env.DOCKER_IMAGE_TAG }}
์ด๋ฏธ์ง๋ฅผ ๋น๋ ๋ฐ docker private repository์ push ํ๊ณ , ssh ๋ช ๋ น์ด๋ก ๋ฐฐํฌ ์๋ฒ์์ ์ด๋ฏธ์ง๋ฅผ ๋ฐ์ ํ ์ปจํ ์ด๋๋ฅผ ์คํ์ํค๋ ๊ณผ์ ์ ํฌํจํ๊ณ ์์ต๋๋ค.
ํ์ํ ํ๊ฒฝ๋ณ์๋ค์ docker/build-push-action์์ build-args๋ฅผ ํตํด ๋์ปค ํ์ผ๋ก ์ ๋ฌํด ์ฃผ๋ฉด ๋ฉ๋๋ค.
๐ ๊ฒฐ๊ณผ

์ปจํ ์ด๋์ exec ๋ช ๋ น์ด๋ก ์ ์ ํ pm2 monit์ ํตํด ๋ชจ๋ํฐ๋งํด๋ณด๋ ์ฌ๋ฌ ์์ฒญ์ ์ ์์ ์ผ๋ก ๊ฐ ํ๋ก์ธ์ค์ ๋ถ๋ฐฐํด์ ์ฒ๋ฆฌํ๋ ๊ฒ์ ํ์ธํ ์ ์์์ต๋๋ค.
'NestJS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [NestJS, TypeScript] Swagger API์ Generic Type ์ ์ํ๋ ๋ฐฉ๋ฒ (0) | 2024.11.22 |
|---|---|
| NestJS์ prisma๋ก ํธ๋ฆฌํ๊ฒ CRUD ๊ตฌํํ๊ธฐ (0) | 2024.11.12 |
| Docker๋ก PostgreSQL ์ค์นํ๊ณ NestJS + prisma ์ฐ๋ํ๊ธฐ (0) | 2024.10.31 |
| NestJS ํ๋ก์ ํธ ์์๊ณผ ๊ตฌ์กฐ์ ์ดํด (0) | 2024.10.28 |
์คํ ์ฑํ
๋๊ธ