📌 이 글은 THE SOPT 30기 서버파트 2차 세미나에서 학습한 내용을 다룹니다.
동기(Synchronous)
비동기(Asynchronous)
Node는 대부분의 메서드를 비동기 방식으로 처리합니다.
Javascript는 기본적으로 Single Thread 언어이기에 한번에 한 작업 처리
어떤 이벤트 발생 시, 특정 시점에 도달했을 때 시스템에서 호출하는 함수, 다른 함수의 인자로서 사용
문제점 - Callback Hell 콜백 지옥
setTimeout((name): void => {
serverList = [...serverList, name];
console.log(serverList);
setTimeout((name): void => {
serverList = [...serverList, name];
console.log(serverList);
setTimeout((name): void => {
serverList = [...serverList, name];
console.log(serverList);
}, 500, '서호영');
}, 500, '이동현');
}, 500, '채정아');
Callback Hell 극복 위해 ES2015부터 Node, JS API들이 콜백 대신 Promise로 재구성됨
Promise의 3가지 상태
new Promise()
호출 시 콜백함수 선언resolve()
실행이 정상적으로 이행(fulfilled)된 상태then()
을 통해 전달catch()
를 통해 전달Promise Chaining
여러개의 Promise를 연결하여 사용
<Promise>.then()
: 비동기 작업 완료 시 결과에 따라 함수 호출
<Promise>.catch()
: 체이닝 연결 상태에서 중간에 에러가 났을 때 호출
const restaurant = (callback: () => void, time: number) => {
setTimeout(callback, time);
};
const order = (): Promise<string> => {
return new Promise((resolve, reject) => {
restaurant(() => {
console.log('[레스토랑 진행 상황 - 음식 주문]');
resolve('음식 주문 시작');
}, 1000);
});
};
const cook = (progress: string): Promise<string> => {
return new Promise((resolve, reject) => {
restaurant(() => {
console.log('[레스토랑 진행 상황 - 음식 조리 중]');
resolve(`${progress} -> 음식 조리 중`);
}, 2000);
});
};
const serving = (progress: string): Promise<string> => {
return new Promise((resolve, reject) => {
restaurant(() => {
console.log('[레스토랑 진행 상황 - 음식 서빙 중]');
resolve(`${progress} -> 음식 서빙 중`);
}, 2500);
});
};
const eat = (progress: string): Promise<string> => {
return new Promise((resolve, reject) => {
restaurant(() => {
console.log('[레스토랑 진행 상황 - 음식 먹는 중]');
resolve(`${progress} -> 음식 먹는 중`);
}, 3000);
});
};
order()
.then((progress) => cook(progress))
.then((progress) => serving(progress))
.then((progress) => eat(progress))
.then((progress) => console.log(progress));
출력
[레스토랑 진행 상황 - 음식 주문]
[레스토랑 진행 상황 - 음식 조리 중]
[레스토랑 진행 상황 - 음식 서빙 중]
[레스토랑 진행 상황 - 음식 먹는 중]
음식 주문 시작 -> 음식 조리 중 -> 음식 서빙 중 -> 음식 먹는 중
Promise는 여러개여도 catch는 단일!
Promise.resolve(123)
.then((res) => {
throw new Error('에러 발생');
return 456;
})
.then((res) => {
console.log(res); // 절대 실행 되지 않음!!!
return Promise.resolve(789);
})
.catch((error) => {
console.log(error.message);
});
출력
에러 발생
ES2017부터 제공되는 혁신적인 기능!
Promise가 어느정도 콜백 지옥을 해결했지만, 여전히 코드가 복잡하고 어려움
동기 코드와 아주 유사
이해하기 쉬움
Async: Async는 암묵적으로 Promise를 반환
Await: Promise를 기다림(resolve/reject), async 정의된 내부에서만 사용
Async 함수 형태
// 함수 표현식
const asyncFunction1 = async() => {
}
// 함수 선언식
async function asyncFunction2() {
}
HyperText Transfer Protocol
하이퍼텍스트 문서를 주고 받을 수 있는 프로토콜 (규칙)
HTTP Method | Action | Request Body |
---|---|---|
GET | 조회 | X |
POST | 생성 | O |
PUT | 수정 | O |
PATCH | 일부 수정 | O |
DELETE | 삭제 | X |
응답 코드 | 상태 |
---|---|
200 | 성공 OK |
201 | 성공, 리소스 생성 Created |
204 | 성공, 응답 데이터 없음 No Content |
400 | 요청을 이해하지 못함 Bad Request |
401 | 인증 필요 Unauthorized |
403 | 요청 거부 Forbidden |
404 | 리소스를 찾을 수 없음 Not Found |
409 | 요청 충돌 Conflict |
500 | 서버 내부 오류 Internal Server Error |
503 | 일시적 서버 이용 불가 Service unavailable |
REpresentational State Transfer
서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 방법
리소스 지향 아키텍처 -> 모든 것을 가급적 리소스, 명사로 표현
REST 아키텍처를 준수하는 API
Application Programming Interface
서버 어플리케이션의 기능을 사용하기 위한 방법
구현 방식을 몰라도 서비스가 서로 통신 가능!
그래서 REST API?
URI는 정보의 자원을 표현
자원에 대한 행위는 HTTP Method(GET, POST, PUT, PATCH, DELETE)으로 표현
Uniform Resource Identifier
통합 자원 식별자 (자원을 나타내는 주소)
자원을 나타내는 유일한 주소
Uniform Resource Locator
통합 자원 지시자
특정 서버의 한 리소스에 대한 구체적인 위치 서술
Node.js를 위한 서버 프레임워크
yarn add express
yarn add -D @types/node @types/express
yarn add -D nodemon
tsc --init
Typescript를 Javascript로 컴파일하는 옵션 설정
{
"compilerOptions": {
"target": "es6", // 어떤 버전으로 컴파일
"allowSyntheticDefaultImports": true, // default export가 없는 모듈에서 default imports를 허용
"experimentalDecorators": true, // decorator 실험적 허용
"emitDecoratorMetadata": true, // 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원
"skipLibCheck": true, // 정의 파일 타입 체크 여부
"moduleResolution": "node", // commonJS -> node 에서 동작
"module": "commonjs", // import 문법
"strict": true, // 타입 검사 엄격하게
"pretty": true, // error 메시지 예쁘게
"sourceMap": true, // 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성
"outDir": "./dist", // 트랜스 파일 (.js) 저장 경로
"allowJs": true, // js 파일 ts에서 import 허용
"esModuleInterop": true, // ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용
"typeRoots": [
"./src/types/express.d.ts", // 타입(*.d.ts)파일을 가져올 디렉토리 설정
"./node_modules/@types" // 설정 안할시 기본적으로 ./node_modules/@types
]
},
"include": [
"./src/**/*" // build 시 포함
],
"exclude": [
"node_modules", // build 시 제외
"tests"
]
}
import express, { Request, Response, NextFunction } from 'express';
const app = express(); // express 객체 받아옴
app.get('/', (req: Request, res: Response, next: NextFunction) => {
res.send('Hi! My name is HyeokJoon!');
}); // get -> http method
app.listen('8000', () => {
console.log(`
#############################################
🛡️ Server listening on port: 8000 🛡️
#############################################
`);
}); // 8000 번 포트에서 서버를 실행하겠다 ~
{
"name": "express-example",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "nodemon",
"build": "tsc && node dist"
},
"dependencies": {
"express": "^4.17.3"
},
"devDependencies": {
"@types/express": "^4.17.13",
"@types/node": "^17.0.23",
"nodemon": "^2.0.15"
}
}
yarn run dev // nodemon으로 서버 실행
yarn run build // 프로젝트 build
애플리케이션 엔드 포인트(URI)의 정의, 그리고 URI가 클라이언트 요청에 응답하는 방식
import express, { Request, Response, Router } from 'express';
const router: Router = express.Router();
router.get('/', (req: Request, res: Response) => {
return res.status(200).json({
status: 200,
message: '유저 조회 성공',
});
});
module.exports = router;