Nest.js๋ ํ์ ์คํฌ๋ฆฝํธ๋ฅผ ์ง์ํ๋ ํจ์จ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ ๋ ธ๋์ ์๋ฒ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ ์์ํฌ์ด๋ฉฐ,
OOP,FP,FRP ์์๋ฅผ ๊ฒฐํฉํ๋ ํน์ง์ ๊ฐ์ง๊ณ ์๋ค.
[์ถ์ฒ](https://velog.io/@funnysunny08/Nest.js-%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80)
์ฑ๊ธํคํจํด
: ์ ์ญ ๋ณ์๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๊ฐ์ฒด๋ฅผ ํ๋๋ง ์์ฑ ํ๋๋กํ๋ฉฐ, ์์ฑ๋ ๊ฐ์ฒด๋ฅผ ์ด๋์์๋ ์ง ์ฐธ์กฐ ํ ์ ์๋๋ก ํ๋ ํจํดController
: ์ฒ์ ์์ฒญ์ด ๋ค์ด์ค๋ ์ ๊ตฌ ์ญํ ์ ๋ด๋นํ๋ฉฐ , ๋น์ฆ๋์ค ๋ก์ง์ ๋ฐ๋ก ๋ถ๋ฆฌํ๊ธฐ ์ํด ์ง์ ์ ์ ๋ฐ๋ก ๋ถ๋ฆฌํด๋ ๊ฒ์ด๋ค.
์ฆ, ์ฝ๊ฒ ์ค๋ช ํ๋ฉด ์ข์์ ์๋ดํด์ฃผ๋ ์ง์์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค.
HTTP Request์ ๋ฐ๋ผ ์ด๋ ํ ๋น์ฆ๋์ค ๋ก์ง์ ์ ์ฉ ์ํฌ์ง์ ๋ํ ์ค์ ์ ํ๋ฉด ๋๋ค,
์ฅ์
NestJS๋ ๊ฐ๋ฐ์์ ํ์ด ๊ณ ๋๋ก ํ
์คํธ ๊ฐ๋ฅํ๊ณ , ํ์ฅ ๊ฐ๋ฅํ๋ฉฐ, ๋์จํ๊ฒ ๊ฒฐํฉ๋๊ณ ์ ์ง๊ด๋ฆฌ๊ฐ ์ฌ์ด ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค ์ ์๋ย ์ฆ์ ์ฌ์ฉ ๊ฐ๋ฅํ ์ ํ๋ฆฌ์ผ์ด์
์ํคํ
์ฒ
๋ฅผ ์ ๊ณตํฉ๋๋ค.
๊ทธ ์ธ์๋ย TypeScript๊ธฐ๋ฐ์ Framework
์ด๋ฉฐย Dependency Injection(์์กด์ฑ์ฃผ์
)
,ย Inversion of Control(์ ์ด์ ์ญ์ )
,ย Module
์ ํตํ ๊ตฌ์กฐํ ๋ฑ ์์ฐ์ฑ์ ์ฉ์ดํฉ๋๋ค.
NestJS๋ย TypeScript๋ฅผ ์ ๊ทน์ ์ผ๋ก ๋์ ํ๋ฉด์ ์๋ฒ ์ดํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ์ ๋ฐ์ ๊ฐ๋ฅํ ์ค๋ฅ๋ค์ ์ฌ์ ์ ๋ฐฉ์งํ ์ ์๋๋ก ํฉ๋๋ค. ๋ํ ์ธ๋ถ์ ์ธ Module๋ก ๋๋์ด์ ธ ์๊ธฐ ๋๋ฌธ์ ๋ ๋ฆฝ์ ์ธ Unit Test๋ฅผ ์ฝ๊ฒ ์์ฑ ๊ฐ๋ฅํ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค.
NestJS๋ Module Class๋ฅผ ์ง์ํ๋ฉฐ, ๊ฐ Module์ ๋น์ทํ ๊ธฐ๋ฅ๊ณผ ๊ฐ๋
๋ค์ Class ํ ๊ณณ์ ๋ด์ ์บก์ํํ๊ณ ์๋ก Import๊ฐ ๊ฐ๋ฅํ๋๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ์ด๋ฌํ Module ๊ตฌ์กฐ๋ Architecture๋ฅผย ์กฐ์ง์ (Organize)
์ผ๋ก ๊ฐ์ ธ๊ฐ๊ฒ ํ๊ณ ย ๋์จํ ๊ฒฐํฉ(Loose Coupling)
์ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ดย ํ์ฅ์ฑ(Extensible)
๊ณผย ํ
์คํธ ๊ฐ๋ฅ์ฑ(Testable)
์ ๋์
๋๋ค.
**IoC(Inversion of Control, ์ ์ด์ ์ญ์ )
**
: ์๋ฐ์คํฌ๋ฆฝํธ์์๋ ๊ฐ๋ฐ์๊ฐ new๋ก ์๋ก์ด ๊ฐ์ฒด๋ฅผ ์ง์ ํ์ฌ ๊ณ์ ์ค์ ํด์ฃผ๊ณ ๊ฐ๋ฐ์๊ฐ ์ ์ด๋ฅผ ํ์ง๋ง
Nest ์์๋ Nest ์์์ ์ ์ ํด์ฃผ๋ ๊ฑฐ๋ฅผ ํด์ฃผ๊ณ ์์ผ๋ฏ๋ก ์ ์ด๊ฐ Nest๋ก ๋์ด๊ฐ๋ค. ์ฆ, ๋ชจ๋ ์ ์ด๊ถ์ ํ๋ ์์ํฌ์ ์ปจํ ์ด๋์๊ฒ ๋๊ธฐ๋ ๊ฐ๋ ??
Nest.js ์ค์นํ๊ธฐ
sudo npm i -g @nestjs/cli
yarn add -g @nestjs/cli
cli(command line interface)?? ์ ํธ ์ํธ์์ฉ
-> ํ์ ์ฐ๋ฆฌ ์ปดํจํฐ์์ ํด๋ ์์ด์ฝ์ ํด๋ฆญํด ๋ค์ด๊ฐ ์์ ์๋ ํ์ผ๋ค์ ์์ด์ฝ์ผ๋ก ๋ณด๊ณ , ๋ ๊ทธ๊ฒ๋ค์ ํด๋ฆญํด์ ์คํํฉ๋๋ค.
CLI๋ ํฐ๋ฏธ๋์์ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํด ํด๋๋ฅผ ์ด๊ณ , ํ์ผ์ ์คํํ๋ ๋ฑ์ ์ํธ์์ฉํ๋ ๋ฐฉ์
nest -version // ์ค์น๊ฐ ์๋ฃ๋ฌ๋์ง ๋ฒ์ ํ์ธ
nest new (์ํด๋๋ช
)
ํด๋์ ์จ๊ฒจ์ง ํ์ผ ์ ๊ฑฐํ๊ธฐ
ls -al //(์จ๊ฒจ์ง ํ์ผ๊น์ง ์กฐํ)
rm -rf .git // gitํ์ผ ์ ๊ฑฐ
์ด๋ ๊ฒ ํ๋ฉด ์์ฑ๋ aaa ํด๋์ Nest์ ๋ง์ ํด๋์ ํ์ผ๋ค์ด ์๋์ผ๋ก ์์ฑ๋๋ค. ์ด๋ ๊ฒ ๊ธฐ๋ณธ์ ์ผ๋ก ์ค์นํด์ฃผ๋ ๊ฒ์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ๋ผ๊ณ ํ๋ค.
๋ณด์ผ๋ฌ ํ๋ ์ดํธ( ์ด๊ธฐ ํด๋ ๊ตฌ์กฐ )
: ์๋ก์ด ํ๋ก์ ํธ๋ฅผ ์คํํ ๋ ์ฒ์ ๋ง๋ค์ด์ง Nest ํด๋ ๊ตฌ์กฐ๋ชจ๋ ํ์ฌ๊ฐ ๊ฐ์ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๋ฅผ ๊ฐ์ง๊ณ ์๋ ๊ฒ์ ์๋๋๋ค. ํ์ฌ์ ๋ฐ๋ผ ๊ฐ์ง๊ณ ์๋ ํ์ํ ๊ธฐ๋ฅ๋ค์ด ๋ด์ฅ๋์ด ์๋ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ๊ฐ ์กด์ฌํ๊ฒ ๋๋ ๊ฒ์ ๋๋ค.
eslint ๋ฅผ ๊ฒ์ํด์ ESLint
๋ ์ค์น
.prettierrc
// .prettierrc
{
"singleQuote": true,
"trailingComma": "all"
}
โ โ ๋ก ํต์ผ
์์ผ์ฃผ๋ ๊ฒ์ฝค๋ง( , )
๋ฅผ ์๋์ผ๋ก ๋ถ์ฌ์ฃผ๋ ๊ฒVS Code ์ ์ค์ (Settings) ์ผ๋ก ๋ค์ด๊ฐ์ format on save
๋ฅผ ๊ฒ์
์ฒดํฌ๋ฅผ ํ๊ฒ ๋๋ฉด ์ฝ๋ ์์ ํ ํ์ผ์ ์ ์ฅ ํ๊ฒ๋์์ ๋ prettier๊ฐ ์ ์ฉ
๋๊ฒ ๋๋๋ฐ
์ฐ๋ฆฌ๋ ์ฌ์ฉํ์ง ์์ ๊ฒ์ ๋๋ค. ์๋ํ๋ฉด ์ฒดํฌ ํ ์ฌ๋๋ง ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ ํ์ ์ ํ ๋ ๋ถํธํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
๋ฐ๋ผ์, ๋ชจ๋๊ฐ ์ผ๊ด ์ ์ฉ๋ ์ ์๊ฒ Settings ๋ถ๋ถ์ ์ฝ๋๋ก ๋ง๋ค์ด ๋ณด๊ฒ ์ต๋๋ค.
ํ์ผ์ ์ต์๋จ ์์น
๋ก ์ด๋ํด ์ฃผ์ธ์.
์ต์๋จ ์์น์์ .vscode
ํด๋๋ฅผ ๋ง๋ค์ด ์ฃผ์ธ์
.vscode
ํด๋ ์์ settings.json
ํ์ผ์ ๋ง๋ค์ด ์ฃผ์ธ์.
settings.json
ํ์ผ ์์ VS Code settings ์ ์์ฑํด์ค๋๋ค.
// settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
"editor.formatOnSave": true
: ์ ์ฅํ๋ฉด ํ๋ฆฌํฐ์ด๊ฐ ๋ฐ๋ก ์ ์ฉ๋ ์ ์๊ฒ ์ค์ "editor.defaultFormatter": "esbenp.prettier-vscodeโ
: prettier ํฌ๋ฉง ์ ์ฉ ์ค์ ๐จย import ์์ eslintrc ์๋ฌ๊ฐ ๋ํ๋๋ค๋ฉด eslintrc๊ฐ tsconfig.json
ํ์ผ ์์น๋ฅผ ์ฐพ์ง ๋ชปํด์ ๋ํ๋๋ ์๋ฌ์ด๋ฏ๋ก .eslintrc.js
ํ์ผ์ ์๋์ ๊ฐ์ด ์์ ํด์ฃผ์ธ์!
๐จย ์๋ฌ๊ฐ ๊ณ์ ๋ฐ์๋๋ค๋ฉด ํ์ผ์ ๊ป๋ค๊ฐ ๋ค์ ์ผ์ฃผ์ธ์!
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
include: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
tsconfigRootDir: __dirname
๐๐ปย tsconfig root ํด๋ ์ฃผ์๋ฅผ ํ์ฌ ์คํ์ค์ธ ํด๋ ์์น์์ ์๋ ค์ฃผ๋ ๊ฒ๐จย import ์์ eslintrc ์๋ฌ๊ฐ ๋ํ๋๋ค๋ฉด eslintrc๊ฐ tsconfig.json
ํ์ผ ์์น๋ฅผ ์ฐพ์ง ๋ชปํด์ ๋ํ๋๋ ์๋ฌ์ด๋ฏ๋ก .eslintrc.js
ํ์ผ์ ์๋์ ๊ฐ์ด ์์ ํด์ฃผ์ธ์!
๐จย ์๋ฌ๊ฐ ๊ณ์ ๋ฐ์๋๋ค๋ฉด ํ์ผ์ ๊ป๋ค๊ฐ ๋ค์ ์ผ์ฃผ์ธ์!
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
include: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};
tsconfigRootDir: __dirname
๐๐ปย tsconfig root ํด๋ ์ฃผ์๋ฅผ ํ์ฌ ์คํ์ค์ธ ํด๋ ์์น์์ ์๋ ค์ฃผ๋ ๊ฒSchema first
๋ graphql์ schema๋ฅผ ๋จผ์ ์ ์ํ๊ณ , ๊ทธ ์ ์์ ๋ง๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ ๋งํ๋ค.Schema
์ ์์ฑํใ
ฃใฑ ์ํด์๋ graphql์ data model์ ๋ํ๋ด๊ธฐ ์ํด ๋ง๋ค์ด์ง SDL(Schema Definiton Language)์ ์ฌ์ฉํ๋ค.GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
}),
//์คํค๋ง ์ฐ์ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ์ต์
๊ฐ์ฒด์ typePaths ์์ฑ์ ์ถ๊ฐํฉ๋๋ค.
//typePaths ์์ฑ์ GraphQLModule์ด ์์ฑํ GraphQL SDL ์คํค๋ง ์ ์ ํ์ผ์
//์ฐพ์์ผํ๋ ์์น๋ฅผ ๋ํ๋
๋๋ค.
์ฝ๋ ์ฐ์
์ ๊ทผ ๋ฐฉ์์์๋ ๋ฐ์ฝ๋ ์ดํฐ์ ํ์
์คํฌ๋ฆฝํธ ํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋จผ์ ์์ฑํ ๋ฆฌ์กธ๋ฒ์ ๊ธฐ๋ฐ์ผ๋ก ํด๋น GraphQL ์คํค๋ง๋ฅผ ์๋ ์์ฑํ๋ค.GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'src/common/graphql/schema.gql',
})
//autoSchemaFile ์์ฑ ๊ฐ์ ์๋์ผ๋ก ์์ฑ๋ ์คํค๋ง๊ฐ ์์ฑ๋ ๊ฒฝ๋ก
$ yarn add @nestjs/graphql @nestjs/apollo graphql apollo-server-express
์ค์นํด์ค ๋ค์ app.module.ts ์์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์
@Module({
imports: [
//์์ ๋ณธ
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
//
}),
],
// controllers: [AppController],
// providers: [AppService],
})
export class AppModule {}
//app.module.ts
@Module({
imports: [
BoardModule,
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'src/common/graphql/schema.gql',
}),
],
// controllers: [AppController],
// providers: [AppService],
})
export class AppModule {}
// boards.module.ts
import { Module } from '@nestjs/common';
import { BoardsResolver } from './boards.resolver';
import { BoardsService } from './boards.service';
@Module({
// imports: [],
// controllers: [],
providers: [BoardsResolver, BoardsService],
})
export class BoardModule {}
// boards.resolver.ts
import { Query, Resolver } from '@nestjs/graphql';
import { BoardsService } from './boards.service';
@Resolver()
export class BoardsResolver {
constructor(private readonly boardsService: BoardsService) {}
@Query(() => String)
fetchBoards(): string {
return this.boardsService.qqq();
}
}
// boards.service.ts
import { Injectable } from '@nestjs/common';
@Injectable() // ์์กด์ฑ ๊ด๋ จ ๋ฐ์ฝ๋ ์ดํฐ
export class BoardsService {
qqq(): string {
return 'Hello World!';
}
}
์ ๋ค๋ฆญ์ ์ธ์ด์์ ์ฌ์ฌ์ฉ์ฑ์ด ๋์ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ๋ ๊ฐ์ฅ ๋ง์ด ํ์ฉ๋๋ ํน์ง์ด๋ค.
// 1.๋ฌธ์/์ซ์/๋ถ๋ฆฐ ๊ธฐ๋ณธํ์
const getPrimitive = (arg1: string, arg2: number, arg3: boolean): [boolean, number, string] => {
return [arg3, arg2, arg1];
};
const result1 = getPrimitive("์ฒ ์", 123, true);
//
//
// 2. any ํ์
(์๋ฐ์คํฌ๋ฆฝํธ์ ๋์ผ)
const getAny = (arg1: any, arg2: any, arg3: any): [any, any, any] => {
console.log(arg1 + 100); // any ๋ ์๋ฌด๊ฑฐ๋ ๋ค ๋จ!
return [arg3, arg2, arg1];
};
const result2 = getAny("์ฒ ์", 123, true);
//
//
// 3. unknown ํ์
const getUnknown = (arg1: unknown, arg2: unknown, arg3: unknown): [unknown, unknown, unknown] => {
if (typeof arg1 === "number") console.log(arg1 + 100); // any ๋ณด๋ค๋ ์์
return [arg3, arg2, arg1];
};
const result3 = getUnknown("์ฒ ์", 123, true);
//
//
// 4-1. generic ํ์
(๋ค์ด์ค๋ ํ์
์ ๋ฐ๋ผ์ MyType์ด ์ค์ ๋๋ค.)
function getGeneric<MyType1, MyType2, MyType3>(arg1: MyType1, arg2: MyType2, arg3: MyType3): [MyType3, MyType2, MyType1] {
return [arg3, arg2, arg1];
}
const result4 = getGeneric("์ฒ ์", 123, true);
//
//
// 4-2. generic ํ์
(๋ค์ด์ค๋ ํ์
์ ๊ณ ์ ์ํค๊ธฐ ์ํด์๋ ์๊ท๋จผํธ์ <> ์ถ๊ฐ ํด์ค์ผํ๋ค.)
function getGeneric2<MyType1, MyType2, MyType3>(arg1: MyType1, arg2: MyType2, arg3: MyType3): [MyType3, MyType2, MyType1] {
return [arg3, arg2, arg1];
}
const result5 = getGeneric2<string, number, boolean>("์ฒ ์", 123, true);
//
//
// 4-3. generic ํ์
function forRoot<T1, T2, T3>(arg1: T1, arg2: T2, arg3: T3): [T3, T2, T1] {
return [arg3, arg2, arg1];
}
const result6 = forRoot("์ฒ ์", 123, true);
//
//
// 4-4. generic ํ์
function getGeneric4<T, U, V>(arg1: T, arg2: U, arg3: V): [V, U, T] {
return [arg3, arg2, arg1];
}
const result7 = getGeneric4<string, number, boolean>("์ฒ ์", 123, true);
//
//
// 4-5. generic ํ์
- 4 (ํ์ดํ ํจ์ ์ฌ์ฉ)
const getGeneric5 = <T, U, V>(arg1: T, arg2: U, arg3: V): [V, U, T] => {
return [arg3, arg2, arg1];
};
const result8 = getGeneric5<string, number, boolean>("์ฒ ์", 123, true);
**interface IProfile {
name: string;
age: number;
school: string;
hobby?: string;
}
//1. Partial ํ์
(๋ถ๋ถ:์์ด๋ ๋๊ณ ์์ด๋ ๋๋)
type aaa = Partial<IProfile>;
//2. Required ํ์
(ํ์์ฌํญ)
type bbb = Required<IProfile>;
//3. Pick ํ์
(์ํ๋ ์์ฑ๋ง ๋ฝ์์ ์ฌ์ฉํ๊ณ ์ถ์ ๋)
type ccc = Pick<IProfile, "name" | "age">;
//4. Omit ํ์
(์ํ๋ ์์ฑ ์ ๊ฑฐํ์ฌ ์ฌ์ฉํ๊ณ ์ถ์ใท ๋ )
type ddd = Omit<IProfile, "school">;
//5. Record ํ์
type eee = "์ฒ ์" | "์ํฌ" | "ํ์ด"; // Union ํ์
let child1: eee = "์ฒ ์"; // ์ฒ ์,์ํฌ,ํ์ด๋ง ๋จ
let child2: string = "์ฌ๊ณผ"; // ์ฒ ์ ์ํฌ ํ์ด ์ฌ๊ณผ ๋ฐ๋๋ ๋ค๋จ
type fff = Record<eee, IProfile>; // Record ํ์
//6.๊ฐ์ฒด์ ํค๋ค๋ก Union ํ์
๋ง๋ค๊ธฐ
type ggg = keyof IProfile; // "name" | "age" | "school" | "hobby"์ Unionํ์
์ ๋ง๋ค์ด์ค
let myprofile: ggg = "hobby";
//7. type vs interface ์ฐจ์ด => interface๋ ์ ์ธ๋ณํฉ ๊ฐ๋ฅ
interface IProfile {
candy: number; // ์ ์ธ๋ณํฉ์ผ๋ก ์ถ๊ฐ๋จ
}
// 8.๋ฐฐ์ด๊ฒ ์์ฉ
let profile: Partial<IProfile> = {
candy: 10,
};**
MySQL ์ค์น
brew update
brew install mysql
brew servcies start mtsql
// ๋น๋ฐ๋ฒํธ ์ค์
mysql_secure_installation
๋ณต์กํ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๋๋ ์ง๋ฌธ์ N
์ ์
๋ ฅํ๊ณ ์ํฐ๋ฅผ ์นฉ๋๋ค.
New password:
Re-enter new password:
New password:
๋ผ๊ณ ๋์ค๋ฉด, ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํ๊ณ ์ํฐ๋ฅผ ์ณ์ ์ค์ ํด์ค๋๋ค.
Re-enter new password:
๋น๋ฐ๋ฒํธ๋ฅผ ๋ค์ ์
๋ ฅํฉ๋๋ค.
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.
Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
y๋ฅผ ์ ๋ ฅํด, ์ต๋ช ์ ์ ์ ๋ฅผ ์ญ์ ํ๋๋ฐ ๋์ํฉ๋๋ค
Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.
Disallow root login remotely? (Press y|Y for Yes, any other key for No) : n
n์ ์ ๋ ฅํด root์ ์๊ฒฉ ์ ์์ ํ์ฉํฉ๋๋ค.
By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.
Remove test database and access to it? (Press y|Y for Yes, any other key for No) : n
... skipping.
Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.
n์ ์ ๋ ฅํด test ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ ์งํฉ๋๋ค.
Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.
y๋ฅผ ์ ๋ ฅํด ๋ณ๊ฒฝ๋ ๊ถํ์ ํ ์ด๋ธ์ ์ ์ฉํฉ๋๋ค.
ํฐ๋ฏธ๋์ mysql -u root -p
๋ผ๊ณ ์
๋ ฅํ ๋ค, ๋ฐฉ๊ธ ์ค์ ํ ๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํฉ๋๋ค.
mysql>
์ฒ๋ผ ํฐ๋ฏธ๋์ด ๋ฐ๋๋ฉด ์ฑ๊ณต์
๋๋ค! ๐
brew services // ์๋น์ค ๋ชฉ๋ก ํ์ธ
brew services start mysql // mysql ์คํํ๊ธฐ
brew services stop mysql // mysql ์ ์ง
yarn add @nestjs/typeorm typeorm mysql2
- @nestjs/typeorm : NestJS ์ฉ TypeORM.
- `typeorm` : typeorm ์ต์ ๋ฒ์ ์ผ๋ก ์ค์น.
- `mysql2` : TypeORM ์ MySQL ๋ก ์ฐ๊ฒฐํ๊ธฐ ์ํ ํ๋ก๊ทธ๋จ ์ค์น.
@Module({
imports: [
BoardModule,
GraphQLModule.forRoot<ApolloDriverConfig>({ // ์ฝ๋ํ์
์ฐ์ ๋ฐฉ์
driver: ApolloDriver,
autoSchemaFile: 'src/commons/graphql/schema.gql',
}),
TypeOrmModule.forRoot({ // ์ถ๊ฐ
type: 'mysql', // ๋ฐ์ดํฐ ๋ฒ ์ด์ค ํ์
host: 'localhost', // local ํ๊ฒฝ์ผ๋ก ์งํ
port: 3306, // mysql์ ๊ธฐ๋ณธ port๋ 3306
username: 'root', // mysql์ ๊ธฐ๋ณธ user๋ root๋ก ์ง์
password: 'root', // ๋ณธ์ธ์ mysql password
database: 'myproject', // ์ฐ๊ฒฐํ ๋ฐ์ดํฐ ๋ฒ ์ด์ค๋ช
entities: [Board], // ๋ฐ์ดํฐ ๋ฒ ์ด์ค์ ์ฐ๊ฒฐํ entity
synchronize: true, // entity ํ
์ด๋ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋๊ธฐํํ ๊ฒ์ธ์ง
logging: true, // ์ฝ์ ์ฐฝ์ log๋ฅผ ํ์ํ ๊ฒ์ธ์ง
}),
],
})
export class AppModule {}
fetchBoards API ์ createBoard API ์์ฑ
**// boards.resolver.ts
import { Query, Resolver, Mutation } from '@nestjs/graphql';
import { BoardsService } from './boards.service';
@Resolver()
export class BoardsResolver {
constructor(private readonly boardsService: BoardsService) {}
@Query(() => String, { nullable: true })
fetchBoards(): string {
return this.boardsService.findAll();
}
@Mutation(() => String)
createBoard(): string {
return this.boardsService.create();
}
}**
ํจ์๋ฅผ findAll ๊ณผ create๋ฅผ ๋ง๋ค์ด์คฌ๋ฏ์ด boardservice ์์๋ ๊ทธ ํจ์์ ๋ํ ๋ก์ง์ ๋ง๋ค์ด์ค๋ค,
// boards.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class BoardsService {
findAll(): string {
// 1. ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ก์ง => DB์ ์ ์ํด์ ๋ฐ์ดํฐ ๊บผ๋ด์ค๊ธฐ
// 2. DB์์ ๊บผ๋ด์จ ๊ฒฐ๊ณผ๋ฅผ ๋ธ๋ผ์ฐ์ ์ ์๋ต(response) ์ฃผ๊ธฐ
return '์กฐํ์ ์ฑ๊ณตํ์์ต๋๋ค.';
}
create(): string {
// 1. ๋ธ๋ผ์ฐ์ ์์ ๋ณด๋ด์ค ๋ฐ์ดํฐ ํ์ธํ๊ธฐ
//
// 2. ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ๋ ๋ก์ง => DB์ ์ ์ํด์ ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ
//
// 3. DB์ ์ ์ฅ์ด ์ ๋์ผ๋ฉด, ๊ฒฐ๊ณผ๋ฅผ ๋ธ๋ผ์ฐ์ ์ ์๋ต(response) ์ฃผ๊ธฐ
return '๊ฒ์๋ฌผ ๋ฑ๋ก์ ์ฑ๊ณตํ์์ต๋๋ค!!';
}
์ ๋ฐ๋ก ์ด๋ ๊ฒ ๋ถ๋ฆฌํ๋๋??? service๋ฅผ ๋ง๋๋ ์ด์ ๋ ์ค๋ณต๋๋ ๋ถ๋ถ๋ค์ ๋ค๋ฅธ API์์๋ ์ฌ์ฉํ๊ธฐ ์ํจ์ผ๋ก ์ค์ ๋ก ์ฌ์ฉ๋๋ ๊ณณ์ resolver ์์ ์ต์ข ์ ์ผ๋ก ์ฌ์ฉ๋๊ฒ ๋๋ค.
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
// ๋งจ์ ๊ณจ๋ฑ
์ด๋ mysQL ์๋๊ณจ๋ฑ
์ด๋ graphQL
@Entity()
@ObjectType()
export class Board {
@PrimaryGeneratedColumn('increment')
@Field(() => Int)
number: number;
@Column()
@Field(() => String)
writer: string;
@Column()
@Field(() => String)
title: string;
@Column()
@Field(() => String)
contents: string;
}
boards.resolver.ts ์์
// boards.resolver.ts
import { Resolver, Query, Mutation } from '@nestjs/graphql';
import { BoardsService } from './boards.service';
import { Board } from './entities/board.entity';
@Resolver()
export class BoardsResolver {
constructor(private readonly boardsService: BoardsService) {}
@Query(() => [Board], { nullable: true })
fetchBoards(): Board[] {
return this.boardsService.findAll();
}
@Mutation(() => String)
createBoard(
@Args('writer') writer: string, //@Args ๋ ์ธ์๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์ถ๊ฐํด์ค๋ ํด์ค๋ค.
@Args('title') title: string, //@Args์ gqlํ์
์ด๊ณ ๊ทธ ๋ค๋ ํ์
์คํฌ๋ฆฝํธ ํ์
์ ์๋ฏธ!
@Args('contents') contents: string,
): string {
return this.boardsService.create({ writer, title, contents });
}
}
์์์ ๋ฆฌ์กธ๋ฒ์์ ๊ฐ๊ฐ์ ์ธ์๋ฅผ ๋ฝ์์ ์ถ๊ฐํ์๋ค. ์ง๊ธ์ ์ธ๊ฐ๋ฐ์ ์ ๋ ฅ์ ์ํ์ง๋ง ๋ ๋ณต์กํ๊ณ ๋ง์ ์ ๋ ฅ์ ๋ฐ์์ผ ํ๋ค๋ฉด ์ด๋ฐฉ์์ ํจ์จ์ ์ด์ง ์๋ค. ๋ ๋ง์ ์ ๋ ฅ์ ๋ฐ์์ผ ํ๋ค๋ฉด ๊ฐ์ฒด๋ก ๋ฌถ์ด์ ์ ๋ฌ๋ฐ๋ ๋ฐฉ์์ด ๋ ํจ์จ์ ์ด๋ค.
dto
ํด๋๋ฅผ ์์ฑํ์ฌ create-board.input.ts
ํ์ผ์ ์์ฑ
dto
: ๋ฐ์ดํฐ ์ ์ก ๊ฐ์ฒด, ์ฆ ๋คํธ์ํฌ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ค ์์ผ๋ก ๋ณด๋ผ์ง๋ฅผ ์ ์ํ ๊ฐ์ฒด์ด๋ค.
// create-board.input.ts
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateBoardInput {
@Field(() => String)
writer: string;
@Field(() => String)
title: string;
@Field(() => String)
contents: string;
}
boards.resolver.ts ์ ์ ์ฉ ์์ผ์ฃผ๊ธฐ.
// boards.resolver.ts
import { Query, Resolver, Mutation, Args } from '@nestjs/graphql';
import { BoardsService } from './boards.service';
import { CreateBoardInput } from './dto/createBoard.input';
import { Board } from './entities/board.entity';
@Resolver()
export class BoardsResolver {
constructor(private readonly boardsService: BoardsService) {}
@Query(() => [Board], { nullable: true })
fetchBoards(): Board[] {
return this.boardsService.findAll();
}
@Mutation(() => String)
createBoard(
// @Args('writer') writer: string,
// @Args('title') title: string,
// @Args('contents') contents: string,
===> ์ถ๊ฐ
@Args('createBoardInput') createBoardInput: CreateBoardInput,
): string {
return this.boardsService.create({createBoardInput});
}
}
boards.service.ts ์์
create({ createBoardInput }: IBoardsServiceCreate): string {
// 1. ๋ธ๋ผ์ฐ์ ์์ ๋ณด๋ด์ค ๋ฐ์ดํฐ ํ์ธํ๊ธฐ
console.log(createBoardInput.writer);
console.log(createBoardInput.title);
console.log(createBoardInput.contents);
// 2. ๋ฐ์ดํฐ๋ฅผ ๋ฑ๋กํ๋ ๋ก์ง => DB์ ์ ์ํด์ ๋ฐ์ดํฐ ์ ์ฅํ๊ธฐ
//
// 3. DB์ ์ ์ฅ์ด ์ ๋์ผ๋ฉด, ๊ฒฐ๊ณผ๋ฅผ ๋ธ๋ผ์ฐ์ ์ ์๋ต(response) ์ฃผ๊ธฐ
return '๊ฒ์๋ฌผ ๋ฑ๋ก์ ์ฑ๊ณตํ์์ต๋๋ค!!';
}
}
// board-services.interface.ts
import { CreateBoardInput } from '../dto/create-board.input';
export interface IBoardsServiceCreate {
createBoardInput: CreateBoardInput;
}
์๊น TypeOrm ์ค์น๋ถ๋ถ์์ ๊ฐ์ธ์ ๋ณด๋ค์ด ์กด์ฌํ๋ค ์ด๋ฅผ ์ํด์ env ์ฒ๋ฆฌํด์ค์ผํ๋ค.
.env ์ .env.docker ์ envํ ๊ฒ๋ค์ ์ ๋ฆฌํ๊ณ module.ts ํ์ผ๊ณผ ์ฐ๊ฒฐํด์ค๋ค.
์ฐ๊ฒฐ ํ๊ธฐ์ nest ์์ ์ง์ํด์ฃผ๊ณ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ๊ฐ๋ฅผ ์ค์นํด์ผ ํ๋ค.
yarn add @nestjs/config
// app.module.ts
@Module({
imports: [
BoardsModule,
ConfigModule.forRoot(), ==> env ์ถ๊ฐ
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'src/commons/graphql/schema.gql',
}),
TypeOrmModule.forRoot({ ==> process.env ์ฒ๋ฆฌํด์ ๊ฐ์ธ์ ๋ณด ๋ณด์
type: process.env.DATABASE_TYPE as 'mysql',
host: process.env.DATABASE_HOST,
port: Number(process.env.DATABASE_PORT),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_DATABASE,
entities: [Board],
synchronize: true,
logging: true,
}),
],
})
export class AppModule {}