치춘짱베리굿나이스
Express로 간단한 서버 만들기 본문
Express로 간단한 서버 만들기
Express - Node.js 웹 애플리케이션 프레임워크
프론트엔드 지망이라고 평생 안써볼 줄 알았지? 하하 맞아라 익스프레스 빔
이걸 쓸 날이 이렇게 빠르게 올 줄은 몰랐다
간단한 웹서버를 만들어야 할 일이 있어서 바로바로 삽질도 기록할 겸 이렇게 노션을 켰다 (물론 업로드되는 곳은 티스토리지만,,)
설치
$> npm install express
$> yarn install express
npm 링크
yarn 링크
설명
Nest.js와 백엔드 프레임워크 쌍두마차를 달리는 그 Express이다
Nest.js도 Express를 기반으로 이런저런 기능을 추가한 프레임워크라 아들내미같은 느낌이다
Redux와 Redux-saga, Redux toolkit과 같은 느낌인 듯 싶다
Nest.js와의 차이점
Express
- Express는 바닐라 프레임워크 느낌이라 자유도가 높고 가볍게 쓰기에는 괜찮지만, 복잡한 서버를 구축하기 위해서는 이런저런 라이브러리를 추가하거나 기능을 직접 구현해야 한다
- Typescript를 사용하기 위해선 추가 설정이 필요하다
- 많은 사람들이 사용하고 있는 만큼 정보를 많이 얻을 수 있다
Nest.js
- Nest.js는 많은 기능들이 이미 내장되어 있어 복잡한 서버를 만들 때에도 꽤나 도움이 된다
- 기본적으로 Typescript를 지원하며, 바닐라 JS로도 가능하다
Nest.js
가 Express
의 업그레이드 버전인 듯 하지만 우리는 ‘간단한 서버' 를 만들 것이기 때문에 Express
로 쉽고 빠르게 서버를 만들어 보자
0. nodemon - 코드 변경될 때마다 자동으로 서버 재시작하기
노드의 악마
…는 아니고 node monitor의 약자라고 한다 🫥 근데 어느정도 작명을 의도하긴 한 듯 아이콘에 악마뿔이 달려있다
nodemon을 통해서 특정 Node.js 코드를 실행시키면 코드 (또는 하위 로직) 에 변경점이 생겼을 때 수동으로 서버를 재시작해줄 필요 없이 알아서 서버를 재시작해준다
$> npm install -g nodemon
전역으로 nodemon
을 설치해주자
nodemon
명령어를 사용하기 위함이다
$> nodemon [실행시키려는 소스 파일]
실행할 때는 nodemon 명령어 뒤에 소스파일 이름을 명시하자
명시하지 않으면 (또는 파일이 존재하지 않으면) nodemon은 현재 위치 (pwd
) 에서 index.js
를 찾아서 실행시킨다
index.js
마저도 없으면 오류가 난다..
"scripts": {
[원하는 명령어]: "nodemon [실행시키려는 소스 파일]"
}
$> npm run [원하는 명령어]
$> yarn [원하는 명령어]
손쉬운 사용을 위해 package.json
에 스크립트 실행 명령어를 등록해주자
npm run [명령어]
또는 yarn [명령어]
로 빠르게 실행시킬 수 있다
코드에서 느낌표 하나를 추가한 뒤 저장하니 코드를 재실행한다
1. 정말정말 간단한 서버 열기
import express from "express";
const app = express();
const port = 8080;
app.listen(port, () => {
console.log(`${port} 포트에서 서버를 열었어요`);
});
express
모듈을 가져온다 (import
)express
모듈을 통해app
을 생성한다- 이
app
은express
내장 메서드들을 활용하여 요청을 듣거나 다양한 처리를 할 수 있다 GET
,POST
,PUT
등의 API들 또한 메서드를 통해 구현할 수 있다
- 이
port
상수를 선언한다- 상수 선언 없이 메서드의 인자에 포트번호를 그대로 넣을 수도 있지만, 쉽게 관리하기 위해 변수로 빼는 것이 나중에 앱이 커졌을 때 대처하기 쉽다
app
이 지정한 포트에서 연결을 듣도록listen
메서드를 사용한다- app은 해당 포트에 귀를 열고, 요청을 들을 (받을) 준비가 되면 콜백 함수를 실행한다
- 간단하게 서버가 열렸다는 문구를 출력해 주었다
와! 서버를 만들었다
아직 GET
도 POST
도 아무것도 없는 휑한 서버긴 한데 아무튼 서버를 시작했다
GET
없는 서버도 서버다!!!!
2. GET 메서드 추가하기
하지만 GET조차 안되는 서버는 초라하기 짝이 없다
localhost:[포트번호]
으로 접속해도 GET할 수 없다면서 아무것도 안 나온다
존재하지 않는 메서드니까 404 에러가 나온다… 정말 초라하다
GET이라도 추가해 주자
간단한 문자열 전송
app.get("/", (req, res) => {
console.log("GET 요청을 받았습니다?");
res.send(`Hello World! from port ${port}`);
});
아까 만들었던 app
의 get
메서드를 사용한다
- 이 서버는
“/”
이라는 URL을 통해 GET 요청을 받을 수 있다 (보통 루트 URL이라 한다) GET
요청을 받으면 콜백 함수를 호출하고, 내부 동작을 수행한다
콜백 함수에는 대충 콘솔에 문자열을 출력하고, 클라이언트 측에 데이터를 보내는 기능을 담았다
req
는 클라이언트로부터 받은 요청이 들어가고,res
는 클라이언트에 전송할 응답을 의미한다
우리는 res.send
메서드를 통해 클라이언트에 응답을 보내게 된다
응답을 받은 클라이언트는 나름대로 데이터를 처리한다
브라우저같은 경우 데이터가 html 타입이 아닐 경우 body
태그에 데이터를 그대로 넣어 출력하며, html 타입인 경우 그 html을 렌더링하여 출력한다
기타 클라이언트 (fetch를 통한 요청 전송 등을 한 경우) 는 코딩된 방식으로 데이터를 처리하여 출력하거나 저장한다
브라우저로 요청을 보냈을 경우, 개발자 도구에서 요청과 응답 헤더, 본문 모두 볼 수 있다
- 요청 헤더에는 요청 메서드, URL, 캐시나 쿠키, User-Agent (쓰는 브라우저 정보 등) 등이 포함되어 있다
- 응답 헤더에는 요청 메서드, URL, 상태 코드, 클라이언트로 보낼 데이터의 본문 정보 (타입, 시간, 길이 등 메타데이터) 등이 포함되어 있다
미리보기 를 누르면 응답 본문의 미리보기를 볼 수 있고, 응답 을 누르면 응답 본문을 볼 수 있다
대부분의 내용은 HTTP 표준에 맞게 어느정도 자동으로 구성되어 보내지고, 서버-클라이언트간 합의하에 몇몇 키-값 쌍을 헤더나 본문에 실어 보낼 수 있다
html 코드 전송
app.get("/", (req, res) => {
console.log("GET 요청을 받았습니다?");
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="background-color: cyan">하이하이</div>
</body>
</html>`);
});
이 예시는 html 코드를 클라이언트에 전송하고, 브라우저는 이 코드를 그대로 렌더링하는 것을 볼 수 있다
스타일도 제대로 적용되었다
개발자 도구를 보면 우리가 전송한 html 데이터와 렌더링된 html 데이터가 같음을 알 수 있다
req, res 인자의 의미
각각을 출력해보면 req
는 IncomingMessage
형식의 인스턴스, res
는 ServerResponse
형식의 인스턴스가 들어있다
IncomingMessage
인스턴스는 서버에서 생성되지만 데이터 자체는 클라이언트의 요청에 관련된 내용이며, 클라이언트에서 보낸 요청의 헤더 (headers
프로퍼티), 전송한 데이터, 요청 방식 (메서드), 호스트나 프로토콜 정보 등이 담겨 있다ServerResponse
인스턴스는 서버가 생성하며, 이번 요청에 대한 응답으로 보낼 상태 코드와 메시지 (statusCode
,statusMessage
프로퍼티), 헤더를 설정할 수 있는 메서드들 (setHeader
등), URL이나 API 메서드 정보 등이 담겨있다
출력해보면 IncomingMessage
나 ServerResponse
나 엄청나게 방대하고 Symbol들의 향연이니 굳이 출력하진 말자
https://nodejs.org/api/http.html#class-httpincomingmessage
https://nodejs.org/api/http.html#class-httpserverresponse
각 클래스에 대한 설명 (몇몇 메서드나 프로퍼티 등) 은 공식 문서에 자세히 나와 있다
Express 전용 메서드가 아니라 http
모듈에 내장된 메서드라 Node.js에서 지원해준다
res.send, res.json 메서드 실험해보기
res.send()
는 인자로 들어온 값에 따라 형식 (Content-type
) 을 바꿔 전송한다res.json()
이라는 녀석도 있다는데 이 친구는 인자로 어떤 값이 들어오든 json 형식으로 바꿔준다고 한다res.end()
는 데이터를 보내지 않고 응답을 보낼 때 사용한다 (상태값 등 기본적인 데이터만 들어간다)
res.send("Hello World! from port 8080");
일반 문자열을 보내면 Content-Type
은 text/html
이 된다
res.send({ text: "console.log('hello')" });
객체 형식을 보내면 Content-Type
은 application/json
이 되며, 브라우저에서 받을 시 화면에 출력되는 폰트도 약간 바뀐다
const data = fs.readFileSync("./index.js");
res.send(data);
파일을 열어서 그 내용물 (비트스트림) 을 전송해 보았다
갑자기 파일이 다운로드된다 (헉)
Content-Type
은 application/octet-stream
이라고 한다
octet이 8이라는 뜻이고 stream이 비트 스트림을 의미하니 대충 8비트 단위의 이진 데이터를 의미한다
다운로드된 파일을 열어보면 우리가 전송했던 index.js
의 내용이 나온다
다른 사이트들을 보면 image나 css 등의 데이터 형식도 보인다
이런 형식들은 파일로 취급되어 요청을 보내면 다운로드 기능이 동작하는 것 같다
res.json("hello world");
전혀 JSON 형식으로 보이지 않는 값을 res.json
메서드를 통해 전송해 보면, 전혀 JSON 형식 값이 아님에도 불구하고 타입이 application/json
으로 전송된다
이처럼 res.json
메서드는 타입을 무조건 application/json
으로 고정시킨다
res.end();
마지막으로 res.end 메서드를 사용해 보니 아예 Content-Length
가 0으로 고정되고, Content-Type
은 존재하지도 않는다
서버 응답 코드 정도만 보내고 데이터를 보내고 싶지 않을 때 사용한다
3. 템플릿 엔진 없이 간단하게 서버사이드 렌더링 하기
app.get("/", (req, res) => {
console.log("GET 요청을 받았습니다?");
// console.log(req);
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div style="background-color: cyan">${[1, 2, 3, 4, 5].reduce(
(acc, cur) => (acc += cur),
0
)}</div>
</body>
</html>`);
});
위의 방법을 사용하되 백틱을 쓰면 중간에 자바스크립트 변수들을 넣어줄 수 있다
for, if 문 등은 사용할 수 없지만, 삼항연산자나 배열 메서드 등을 이용하여 특정 문자열을 렌더링할 수 있다
개발자도구로 보면, 모든 연산값이 계산된 상태로 클라이언트에 전송되기 때문에 클라이언트는 그대로 출력하기만 하면 된다
서버사이드 렌더링은 대부분의 연산 및 변수 대입을 서버단에서 진행한 뒤 렌더링이 완료된 마크업만을 클라이언트에 내려주는 방식으로, 위의 케이스도 배열의 모든 값을 더하는 연산 뿐이라 단순하지만 서버사이드 렌더링의 의미에 부합한다
위의 경우는 매우 간단하기 때문에 res.send
의 인자로 문자열 형태의 마크업 데이터를 밀어넣어도 괜찮았지만, 데이터가 복잡할 경우 대부분 pug
, ejs
등 템플릿 엔진을 통해 HTML 형식 그대로 사용하여 서버사이드 렌더링을 수행한다
4. POST 메서드 추가하기
app.get("/", (req, res) => {
console.log("GET 요청을 받았습니다?");
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="" method="post">
<input type="text" name="input" placeholder="아무거나 입력해보세요" size="100"/>
<button type="submit">전송하기</button>
</form>
</body>
</html>`);
});
요런 간단한 페이지를 서버에서 렌더링해 보자
폼이 있긴 하지만 입력칸이 단 하나밖에 없어 매우 간단하다
전송하기 버튼을 누르면 데이터를 전송하는… 기가막히게 간단한 폼이다
당연히 우리는 POST
메서드를 구현하지 않았기 때문에 서버는 데이터를 받았지만 데이터의 목적지에 해당하는 메서드나 경로가 존재하지 않아 404 에러를 띄운다
POST
메서드를 구현하고 서버에서 직접 데이터를 받아보자
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
응답을 받기 전 사전에 사용설정을 해야 하는 모듈이 두 개 있는데, express.json()
과 express.urlencoded()
이다
두 모듈 다 특정 타입의 요청 데이터를 파싱하는 데에 사용되며, 미리 설정하지 않으면 데이터를 읽어 객체처럼 사용하려 할 때 데이터를 파싱하지 못해 undefined
가 나온다
express.json()
은 JSON 형태의 데이터를 파싱해준다express.urlencoded()
는x-www-form-urlencoded
형식의 데이터를 파싱해준다
위의 예제는 form
태그를 통해 데이터를 취합하여 전송하므로, Content-Type
이 x-www-form-urlencoded
로 고정되고, 이를 파싱하기 위해 필요한 것이 express.urlencoded()
인 것이다
app.post("/", (req, res) => {
console.log(`수신받은 문자열: \t${req.body.content}`);
data.push(req.body.content);
res.send(`저장 완료!!`);
});
POST
구현 방법은 전체적으로 get
과 비슷하다만 app.post
메서드를 사용한다
req
(요청 = request) 에는 클라이언트로부터 받은 메시지가 들어있으므로 요청의 body
에서 content
를 꺼낸다
아직 데이터베이스나 테이블을 구축하지 않았기 때문에 간단하게 data
라는 배열에 데이터를 넣도록 설정하였다
클라이언트는 서버 측에 저장이 되었다면 저장 완료!!
라는 문구를 전송받아 화면에 출력한다
서버는 수신받은 문자열을 출력하고, data
배열에 push
한다
data
배열은 서버가 꺼지기 전까지 내부 데이터가 유지된다
데이터 수신 후 이전 페이지로 돌아가기
지금은 데이터를 수신받으면 저장 완료!!
문구를 전송해서 클라이언트에 출력하기만 하고, 이전 페이지로 돌아가지 않는다
app.post("/", (req, res) => {
console.log(`수신받은 문자열: \t${req.body.content}`);
data.push(req.body.content);
res.send(`<script>location.href = "/";</script>`);
});
res.send
할 때 문자열을 보내지 말고 html 태그로 자바스크립트 코드를 감싸서 보내자 (script
태그)
location.href
프로퍼티는 현재 접속중인 페이지의 경로를 가리키며, 이를 수정하면 다른 페이지로 이동할 수 있다
location.href = “/”;
코드를 통해 루트 경로로 다시 돌아가게끔 해 주자
데이터 수신 후 알림 창 띄우기
app.post("/", (req, res) => {
console.log(`수신받은 문자열: \t${req.body.content}`);
data.push(req.body.content);
res.send(`
<script>
alert("데이터가 잘 전송되었어요");
location.href = "/";
</script>`);
});
그냥 리디렉션만 하거나 페이지 이동만 하면 데이터가 잘 전송되었는지 알기 힘들다
script
태그 내에 alert
함수를 통해 알림창을 띄워주자
요청 (req) 의 형식
이 포스팅에 간단하게 적었었지만 HTTP에서의 요청과 응답은 크게 헤더와 페이로드 (본문) 으로 이루어진다
- Node.js에서 헤더를 가져오고 싶다면
IncomingMessage
클래스의header
프로퍼티에 접근한다 - 페이로드 (본문) 을 가져오고 싶다면
IncomingMessage
클래스의body
프로퍼티에 접근한다
form 태그의 action 속성
<form action="asd" method="post">
form
의 action
속성은 폼 데이터를 서버로 보낼 때 서버상에서 데이터가 도착해야 하는 URL이다
action
속성에 값을 주니 해당 경로를 상대경로로 판단하여 그 쪽으로 데이터를 전송한 것을 볼 수 있다
http://
또는 https://
로 시작하는 경로를 넣어주면 절대경로로 판단한다
5. 전송한 데이터 서버사이드에서 화면에 렌더링하기
...
<body>
<form action="" method="post">
<input type="text" name="content" placeholder="아무거나 입력해보세요" size="100"/>
<button type="submit">전송하기</button>
</form>
<ul>
${data.map((item, index) => `<li>${index}\t${item}</li>`).join("")}
</ul>
</body>
...
폼 하단에 리스트를 만들어 보자
data
배열에 데이터를 담았었으니 해당 배열의 값들을 <li>
태그를 이용하여 감싸주고 출력한다
map
메서드를 이용하여 배열의 모든 값을 태그에 담아 새로운 배열을 만들고, 이를 join
해준다
전송할 때마다 아래에 데이터가 하나씩 추가된다
스타일을 하나도 적용하지 않았기 때문에 옆에 점이 찍히고 패딩이 크게 들어가긴 하지만 아무튼 사용자는 내가 보낸 데이터를 바로 볼 수 있다
간략하게나마 게시판이..? 완성되었다 굿
다음 포스팅에서는 데이터베이스를 짜거나 서버사이드 렌더링 엔진들을 사용해보자
참고자료
HTTP | Node.js v18.8.0 Documentation
Express res.send() vs res.json() vs res.end() 비교
Vue SSR 제대로 적용하기 (feat. Vanilla SSR)
'ServerSide > Nodejs' 카테고리의 다른 글
nodejs, npm, npx, nvm (0) | 2022.04.15 |
---|