- 프론트엔드 : Typescript, React, MaterialUI
- 백엔드 : Node.js, Nest.js, TypeORM
Docker Compose File
$ sudo snap install docker
$ sudo apt install docker-compose
$ docker-compose -f task-management-compose.yml up -d
$ docker ps
// adminer : localhost:8888
System: PostgreSQL;
Server: db;
Username: postgres;
Password: 123456;
NestJS
$ yarn global add @nestjs/cli typeorm
$ nest new '프로젝트' (yarn)
- PostgreSQL : 오픈 소스 객체-관계형 데이터베이스 시스템(ORDBMS)
DB TypeORM
$ npm install --save @nestjs/typeorm pg
// src/ormconfig.ts
const config: ConnectionOptions = {
username: "postgres",
password: "123456",
database: "taskManager",
};
$ nest g module database
// src/database/database.module.ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import * as ormconfig from "../ormconfig";
@Module({ imports: [TypeOrmModule.forRoot(ormconfig)] })
export class DatabaseModule {}
// package.json
"scripts": {
"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js --config src/ormconfig.ts",
"typeorm:migrate": "yarn typeorm migration:generate -n",
"typeorm:run": "yarn typeorm migration:run",
"typeorm:revert": "yarn typeorm migration:revert"
}
$ yarn typeorm:migrate Init
src/migrations
가 생성이 된다.
// src/entity/task.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm";
export enum TaskStatus {
Created = 0,
InProgress = 1,
Done = 2
}
@Entity()
export class Task {
@PrimaryGeneratedColumn()
id: number;
@Column({ nullable: true, length: 64 })
title: string;
@Column({ nullable: true, length: 1024 })
description: string;
@Column({ nullable: false, default: TaskStatus.Created })
status: TaskStatus;
}
$ yarn typeorm:migrate AddTask
$ yarn typeorm:run
$ nest generate module task
src/task/task.module.ts
$ nest g controller task
src/task/task.controller.ts
src/task/task.controller.spec.ts
$ nest g service task
src/task/task.service.ts
src/task/task.service.spec.ts
// src/task/task.module.ts
import { Task } from "../entity/task.entity";
@Module({
imports: [TypeOrmModule.forFeature([Task])],
})
export class TaskModule {}
// src/task/task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TaskDTO } from 'src/dto/task.dto';
import { Task, TaskStatus } from 'src/entity/task.entity';
import { Repository } from 'typeorm';
import { CreateTaskDTO } from '../dto/create-task.dto'
@Injectable()
export class TaskService {
constructor(@InjectRepository(Task) private taskRepository: Repository<Task>) {}
public async createOne(createTaskRequest: CreateTaskDTO) {
const task: Task = new Task();
task.title = createTaskRequest.title;
task.description = createTaskRequest.description;
task.status = TaskStatus.Created;
// DB에 저장
await this.taskRepository.save(task);
const taskDTO = new TaskDTO();
taskDTO.id = task.id;
taskDTO.title = task.title;
taskDTO.description = task.description;
taskDTO.status = task.status;
return taskDTO;
}
}
// src/dto/create-task.dto.ts
export class CreateTaskDTO {
title: string;
description?: string;
}
// src/dto/task.dto.ts
import { TaskStatus } from "src/entity/task.entity";
export class TaskDTO {
id: number;
title: string;
description: string;
status: TaskStatus;
}
// src/task/task.controller.ts
import { Controller, Body, Post } from '@nestjs/common';
import { CreateTaskDTO } from 'src/dto/create-task.dto';
import { TaskService } from './task.service';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Post()
public async createOne(@Body() createTaskRequest: CreateTaskDTO) {
const resp = await this.taskService.createOne(createTaskRequest);
return resp;
}
}
$ yarn add rxjs
$ yarn start:dev
dist
폴더가 생성된다.
콘솔확인 : Mapped {/tasks, POST} route
- 요청 : POST 요청(JSON) :
http://localhost:3000/tasks
{
"title": "Task #1",
"description": "My first task"
}
{
"title": "Task #2",
"description": "My Second task"
}
- 응답
{
"id": 1,
"title": "Task #1",
"description": "My first task",
"status": 0
}
{
"id": 2,
"title": "Task #2",
"description": "My Second task",
"status": 0
}
// src/task/task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TaskDTO } from 'src/dto/task.dto';
import { Task, TaskStatus } from 'src/entity/task.entity';
import { Repository } from 'typeorm';
import { CreateTaskDTO } from '../dto/create-task.dto'
@Injectable()
export class TaskService {
constructor(@InjectRepository(Task) private taskRepository: Repository<Task>) {}
public async createOne(createTaskRequest: CreateTaskDTO) {
const task: Task = new Task();
task.title = createTaskRequest.title;
task.description = createTaskRequest.description;
task.status = TaskStatus.Created;
// DB 저장
await this.taskRepository.save(task);
const taskDTO = this.entityToDTO(task);
return taskDTO;
}
private entityToDTO(task: Task): TaskDTO {
const taskDTO = new TaskDTO();
taskDTO.id = task.id;
taskDTO.title = task.title;
taskDTO.description = task.description;
taskDTO.status = task.status;
return taskDTO;
}
public async getAll() {
const tasks: Task[] = await this.taskRepository.find();
const tasksDTO: TaskDTO[] = tasks.map(x => this.entityToDTO(x));
return tasksDTO;
}
}
// src/task/task.controller.ts
import { Controller, Get } from '@nestjs/common';
import { TaskService } from './task.service';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Get()
public async getAll() {
const resp = await this.taskService.getAll();
return resp;
}
}
- 요청 : GET요청(JSON)
http://localhost:3000/tasks
- 응답
[
{
id: 1,
title: "Task #1",
description: "My first task",
status: 0,
},
{
id: 2,
title: "Task #2",
description: "My Second task",
status: 0,
},
];
// src/task/task.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TaskDTO } from 'src/dto/task.dto';
import { Task } from 'src/entity/task.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TaskService {
constructor(@InjectRepository(Task) private taskRepository: Repository<Task>) {}
public async getOne(taskId: number) {
const task: Task = await this.taskRepository.findOne(taskId);
if(!task) throw new NotFoundException(`Task with the id ${taskId} was not found`)
const taskDTO: TaskDTO = this.entityToDTO(task);
return taskDTO;
}
}
// src/task/task.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { TaskService } from './task.service';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Get('/:id')
public async getOne(@Param('id') taskId: number) {
const resp = await this.taskService.getOne(taskId);
return resp;
}
}
- 요청 : GET요청(JSON)
http://localhost:3000/tasks/1
- 응답
{
"id": 1,
"title": "Task #1",
"description": "My first task",
"status": 0
}
- 요청 : GET요청(JSON)
http://localhost:3000/tasks/5
- 응답
{
"statusCode": 404,
"message": "Task with the id 5 was not found",
"error": "Not Found"
}
// src/task/task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { TaskDTO } from 'src/dto/task.dto';
import { UpdateTaskDTO } from 'src/dto/update-task.dto';
import { Task } from 'src/entity/task.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TaskService {
constructor(@InjectRepository(Task) private taskRepository: Repository<Task>) {}
public async updateOne(taskId: number, updateTaskRequest: UpdateTaskDTO) {
// fetch and check if the task exists
const task: Task = await this.getOne(taskId);
// check which properties are set in dto
task.title = updateTaskRequest.title || task.title;
task.description = updateTaskRequest.description || task.description;
task.status = updateTaskRequest.status || task.status;
// update the properties on the task
await this.taskRepository.save(task);
// return the task as a dto
const taskDTO: TaskDTO = this.entityToDTO(task);
return taskDTO;
}
}
// src/dto/update-task.dto.ts
import { TaskStatus } from "src/entity/task.entity";
export class UpdateTaskDTO {
title?: string;
description?: string;
status?: TaskStatus;
}
// src/task/task.controller.ts
import { Controller, Body, Param, Put } from '@nestjs/common';
import { UpdateTaskDTO } from 'src/dto/update-task.dto';
import { TaskService } from './task.service';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Put('/:id')
public async updateOne(@Param('id') taskId: number, @Body() updateTaskRequest: UpdateTaskDTO) {
const resp = await this.taskService.updateOne(taskId, updateTaskRequest);
return resp;
}
}
- 요청 : PUT 요청(JSON)
http://localhost:3000/tasks/1
{
"title": "UPDATE Task #1",
"description": "UPDATE My Second task",
"status": 1
}
- 응답
{
"id": 1,
"title": "UPDATE Task #1",
"description": "UPDATE My Second task",
"status": 1
}
// src/task/task.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Task } from 'src/entity/task.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TaskService {
constructor(@InjectRepository(Task) private taskRepository: Repository<Task>) {}
public async deleteOne(taskId: number) {
// fetch and check if the task exists
const task: Task = await this.getOne(taskId);
// delete the task
await this.taskRepository.remove(task);
}
}
// src/task/task.controller.ts
import { Controller, Param, Delete, HttpCode, HttpStatus } from '@nestjs/common';
import { TaskService } from './task.service';
@Controller('tasks')
export class TaskController {
constructor(private readonly taskService: TaskService) {}
@Delete('/:id')
@HttpCode(HttpStatus.NO_CONTENT)
public async deleteOne(@Param('id') taskId: number) {
await this.taskService.deleteOne(taskId);
}
}
-
요청 : DELETE 요청(JSON)
http://localhost:3000/tasks/1
-
확인 : GET 요청(JSON)
http://localhost:3000/tasks
[
{
id: 2,
title: "Task #2",
description: "My Second task",
status: 0,
},
];