치춘짱베리굿나이스

Express로 간단한 서버 만들기 본문

ServerSide/Nodejs

Express로 간단한 서버 만들기

치춘 2022. 9. 3. 22:58

Express로 간단한 서버 만들기

Express - Node.js 웹 애플리케이션 프레임워크

 

Express - Node.js 웹 애플리케이션 프레임워크

Node.js를 위한 빠르고 개방적인 간결한 웹 프레임워크 $ npm install express --save

expressjs.com

프론트엔드 지망이라고 평생 안써볼 줄 알았지? 하하 맞아라 익스프레스 빔

이걸 쓸 날이 이렇게 빠르게 올 줄은 몰랐다

간단한 웹서버를 만들어야 할 일이 있어서 바로바로 삽질도 기록할 겸 이렇게 노션을 켰다 (물론 업로드되는 곳은 티스토리지만,,)

설치

$> npm install express
$> yarn install express

npm 링크

express

 

express

Fast, unopinionated, minimalist web framework. Latest version: 4.18.1, last published: 4 months ago. Start using express in your project by running `npm i express`. There are 64303 other projects in the npm registry using express.

www.npmjs.com

yarn 링크

Contributors

 

https://yarnpkg.com/package/express

Fast, reliable, and secure dependency management.

yarnpkg.com

설명

Nest.js와 백엔드 프레임워크 쌍두마차를 달리는 그 Express이다

Nest.js도 Express를 기반으로 이런저런 기능을 추가한 프레임워크라 아들내미같은 느낌이다

Redux와 Redux-saga, Redux toolkit과 같은 느낌인 듯 싶다

Nest.js와의 차이점

Express

  • Express는 바닐라 프레임워크 느낌이라 자유도가 높고 가볍게 쓰기에는 괜찮지만, 복잡한 서버를 구축하기 위해서는 이런저런 라이브러리를 추가하거나 기능을 직접 구현해야 한다
  • Typescript를 사용하기 위해선 추가 설정이 필요하다
  • 많은 사람들이 사용하고 있는 만큼 정보를 많이 얻을 수 있다

Nest.js

  • Nest.js는 많은 기능들이 이미 내장되어 있어 복잡한 서버를 만들 때에도 꽤나 도움이 된다
  • 기본적으로 Typescript를 지원하며, 바닐라 JS로도 가능하다

Nest.jsExpress의 업그레이드 버전인 듯 하지만 우리는 ‘간단한 서버' 를 만들 것이기 때문에 Express로 쉽고 빠르게 서버를 만들어 보자

0. nodemon - 코드 변경될 때마다 자동으로 서버 재시작하기

nodemon

 

nodemon

Simple monitor script for use during development of a Node.js app.. Latest version: 2.0.19, last published: 2 months ago. Start using nodemon in your project by running `npm i nodemon`. There are 3868 other projects in the npm registry using nodemon.

www.npmjs.com

노드의 악마

…는 아니고 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} 포트에서 서버를 열었어요`);
});
  1. express 모듈을 가져온다 (import)
  2. express 모듈을 통해 app을 생성한다
    • appexpress 내장 메서드들을 활용하여 요청을 듣거나 다양한 처리를 할 수 있다
    • GET, POST, PUT 등의 API들 또한 메서드를 통해 구현할 수 있다
  3. port 상수를 선언한다
    • 상수 선언 없이 메서드의 인자에 포트번호를 그대로 넣을 수도 있지만, 쉽게 관리하기 위해 변수로 빼는 것이 나중에 앱이 커졌을 때 대처하기 쉽다
  4. app이 지정한 포트에서 연결을 듣도록 listen 메서드를 사용한다
    • app은 해당 포트에 귀를 열고, 요청을 들을 (받을) 준비가 되면 콜백 함수를 실행한다
    • 간단하게 서버가 열렸다는 문구를 출력해 주었다

 

와! 서버를 만들었다

아직 GETPOST도 아무것도 없는 휑한 서버긴 한데 아무튼 서버를 시작했다

GET 없는 서버도 서버다!!!!

2. GET 메서드 추가하기

하지만 GET조차 안되는 서버는 초라하기 짝이 없다

localhost:[포트번호] 으로 접속해도 GET할 수 없다면서 아무것도 안 나온다

존재하지 않는 메서드니까 404 에러가 나온다… 정말 초라하다

GET이라도 추가해 주자

간단한 문자열 전송

app.get("/", (req, res) => {
  console.log("GET 요청을 받았습니다?");
  res.send(`Hello World! from port ${port}`);
});

아까 만들었던 appget 메서드를 사용한다

  • 이 서버는 “/” 이라는 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 인자의 의미

각각을 출력해보면 reqIncomingMessage 형식의 인스턴스, resServerResponse 형식의 인스턴스가 들어있다

  • IncomingMessage 인스턴스는 서버에서 생성되지만 데이터 자체는 클라이언트의 요청에 관련된 내용이며, 클라이언트에서 보낸 요청의 헤더 (headers 프로퍼티), 전송한 데이터, 요청 방식 (메서드), 호스트나 프로토콜 정보 등이 담겨 있다
  • ServerResponse 인스턴스는 서버가 생성하며, 이번 요청에 대한 응답으로 보낼 상태 코드와 메시지 (statusCode, statusMessage 프로퍼티), 헤더를 설정할 수 있는 메서드들 (setHeader 등), URL이나 API 메서드 정보 등이 담겨있다

 

출력해보면 IncomingMessageServerResponse나 엄청나게 방대하고 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-Typetext/html이 된다

 

res.send({ text: "console.log('hello')" });

객체 형식을 보내면 Content-Typeapplication/json이 되며, 브라우저에서 받을 시 화면에 출력되는 폰트도 약간 바뀐다

 

const data = fs.readFileSync("./index.js");
res.send(data);

파일을 열어서 그 내용물 (비트스트림) 을 전송해 보았다

갑자기 파일이 다운로드된다 (헉)

Content-Typeapplication/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-Typex-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 기본

이 포스팅에 간단하게 적었었지만 HTTP에서의 요청과 응답은 크게 헤더와 페이로드 (본문) 으로 이루어진다

  • Node.js에서 헤더를 가져오고 싶다면 IncomingMessage 클래스의 header 프로퍼티에 접근한다
  • 페이로드 (본문) 을 가져오고 싶다면 IncomingMessage 클래스의 body 프로퍼티에 접근한다

form 태그의 action 속성

<form action="asd" method="post">

formaction 속성은 폼 데이터를 서버로 보낼 때 서버상에서 데이터가 도착해야 하는 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해준다

 

전송할 때마다 아래에 데이터가 하나씩 추가된다

스타일을 하나도 적용하지 않았기 때문에 옆에 점이 찍히고 패딩이 크게 들어가긴 하지만 아무튼 사용자는 내가 보낸 데이터를 바로 볼 수 있다

간략하게나마 게시판이..? 완성되었다 굿

다음 포스팅에서는 데이터베이스를 짜거나 서버사이드 렌더링 엔진들을 사용해보자


참고자료

Express "Hello World" 예제

점프 투 파이썬

1-3. Nodemon 사용하기

HTTP | Node.js v18.8.0 Documentation

Express res.send() vs res.json() vs res.end() 비교

Vue SSR 제대로 적용하기 (feat. Vanilla SSR)

Express와 함께 템플리트 엔진 사용

[NodeJs] express.json()과 express.urlencoded()의 차이점 알아보기

코딩교육 티씨피스쿨

'ServerSide > Nodejs' 카테고리의 다른 글

nodejs, npm, npx, nvm  (0) 2022.04.15
Comments