-
[Node.js] Express 웹 서버 구축하기Studying/JavaScript 2022. 7. 20. 21:43
개요
Node.js 환경에서 Express 웹 서버를 구축하는 과정을 기록합니다.
사전 개념: JavaScript란?
Web Browser, Server, Mobile 개발이 가능한 멀티 패러다임 프로그래밍 언어입니다.
Script 언어란?
특정한 프로그램 안에서 동작하는 언어를 말합니다. JS의 경우 웹 브라우저 안에서만 동작하기 때문에 스크립트 언어라고 불리게 되죠. 하지만 요즘은 Node.js 를 사용하여 브라우저 바깥에서도 얼마든지 JavaScript를 사용할 수 있습니다.
JavaScript 엔진
JavaScript 코드를 실행할 수 있는 프로그램입니다. 크롬에 내장 된 v8이 대표적이고, 크롬 웹 브라우저와 Node.js 등에서 사용됩니다. 자바스크립트를 바이트코드로 컴파일하고 실행하는 방식을 사용합니다.
ECMAScript
자바스크립트의 표준 사양인 ECMA-262. 프로그래밍 언어의 값, 타입, 객체와 프로퍼티, 함수 등 핵심 문법을 규정하는 규칙입니다.
즉, 자바스크립트는 일반적으로
프로그래밍 언어로서 기본 뼈대를 이루는 ECMAScript와 브라우저가 지원하는 클라이언트 사이드 WebAPI*를 아우르는 개념입니다.
(*WebAPI: DOM, Canvas, XMLHttpRequest 등)
Node.js
구글 v8 JavaScript 엔진으로 빌드 된 자바스크립트 런타임* 환경입니다.
(*런타임: 특정 언어로 만든 프로그램을 실행할 수 있는 환경. 자바스크립트의 런타임 종류로는 웹 브라우저와 Node.js 프로그램이 있다.)
브라우저에서만 동작하던 JavaScript를 브라우저 이외의 환경에서도 동작할 수 있도록 독립시킨 실행 환경이죠.
이제 Node.js 환경만 있다면 JavaScript로 브라우저 뿐만 아닌 서버를 구축하고, 서버에서 js가 작동되도록 만들 수 있습니다.
(실시간 온라인 기능, 로그인, 유저 관리 등 데이터 베이스 기능을 Node.js를 통해 만들 수 있게 되는 거죠)
오늘 서버와 브라우저 간 통신을 주고받을 server.js 파일이 그런 경우입니다. html 파일과 같이 브라우저에서 동작하는 건 아니지만 작성 언어는 자바 스크립트를 사용합니다!
이전에는 Server-Client 웹 사이트를 만들 때 프론트엔드는 JS로 만들고 서버는 Java 등 다른 언어를 써서 만들어야 했는데, 이젠 한 가지 언어로 전체 웹 페이지를 만들 수 있게 되었다고 하네요.
Express
Node.js 웹 애플리케이션 프레임워크*입니다. "서버를 쉽게 짤 수 있도록 도와주는 라이브러리"라고 간단하게 생각하면 됩니다.
(*프레임워크 : 어떤 작업을 쉽게 완성하기 위한 라이브러리 집합. 라이브러리처럼 부분을 도와준다기 보다는, 그 자체로 하나의 뚜렷한 목적을 가진 틀이다.)
Node.js를 이용하여 웹 애플리케이션을 만들기 위한 틀을 제공하는 것입니다.
서버 구축
우선 실습 폴더를 생성하고 터미널을 오픈합니다.
1. 패키지 생성
npm init -y
- npm : 자바스크립트 언어를 위한 패키지 관리자. Node.js에서 사용할 수 있는 모듈들을 패키지화 하여 모아둔 저장소 역할을 합니다. 패키지 설치 및 관리를 위한 CLI(Command Line Interface)를 제공하며 Node.js 설치 시 자동 설치됩니다.
- npm init : pakage.json 을 생성한다. 의존성 패키지를 일괄 처리합니다.
- npm install : 패키지 설치. 설치된 모든 패키지를 모아둔 저장소 node-modules가 생성됩니다.
- npm install --save : 로컬에 모듈을 설치하면서 자동으로 pakage.json을 업데이트 해주는 옵션.
2. Express 라이브러리 설치
아래 명령어로 Express 라이브러리를 설치합니다.
npm i -S express
3. Express CORS* 방식 허용
npm i -S cors
(*CORS : 자신이 속하지 않은 도메인, 프로토콜, 포트에 있는 리소스를 요청하는 cross-origin HTTP 요청 방식)
이걸 허용하지 않으면 자신의 사이트에서 외부 링크로 넘어갈 수 없습니다.
4. nodemon 설치
npm install nodemon --save-dev
실행 파일이 속한 디렉터리를 node가 감시하고 있다가, 파일이 수정 되면 서버를 재실행 하지 않고도 자동으로 반영해주는 확장 모듈입니다. 이를 사용하지 않으면 코드가 바뀔 때마다 매번 새로고침 하여 결과를 봐야 합니다.
- -dev : 개발환경(로컬)에서만 설치하고 사용하겠다는 뜻. 프로젝트 배포 시에는 설치되지 않는다.
- -D : --save-dev의 축약형.
5. Express 서버 구축하기
웹 서버의 역할을 해줄 server.js 파일을 생성합니다.
- server.js
const http = require('http'); // HTTP 모듈 요청 const express = require('express'); // Express 모듈 요청 const app = express(); // express 애플리케이션 생성 const cors = require('cors'); app.get("/", (req, res)=> { res.end("<h1>Hello Node.js world!</h1>"); }) const server = http.createServer(app); // 요청/연결을 처리하는 Server 객체 생성 server.listen(3000, ()=>{ // 3000번 포트 요청을 기다린다. console.log("run on Server http://localhost:3000"); // 연결 시 실행되는 콜백 함수 });
HTTP 모듈에는 각종 요청/연결을 처리하는 Server 객체가 존재합니다.
이를 생성하는 함수가 바로 createServer() 입니다.
서버를 생성한 이후에는 listen() 함수를 통해 포트 번호를 할당 받습니다.
종료할 때는 close()를 호출합니다.
아래 더 나은 Express 서버 파일 정리 링크를 참고하여 server.js 파일을 두 개로 분리해 보았습니다.
- server.js
const http = require('http'); const app = require("./app"); app.get("/", (req, res)=> { res.end("<h1>Hello Node.js world!</h1>"); }) const server = http.createServer(app); server.listen(3000, ()=>{ console.log("run on Server http://localhost:3000"); });
- app.js
const express = require('express'); const cors = require('cors'); class App { constructor() { this.app = express(); this.setStatic(); } } module.exports = new App().app;
6. Express 서버 실행하기
node server.js
위 명령을 통해 server.js 파일을 실행합니다. 로컬 서버가 오픈 되고 설정한 포트 주소 localhost:3000 에 접속하면 아래와 같이 정상 연결 됨을 알 수 있습니다.
7. nodemon 실행하기
! 하지만 6번까지 실행했다면, 앞으로 서버 파일을 수정할 때마다 서버를 재시작 해야합니다.
이때 nodemon 이라는 도구를 사용하면 코드가 바뀔 때마다 자동으로 업데이트를 해줍니다.
server.js 파일을 실행 시킬 때마다 nodemon 명령어를 타이핑 하기 귀찮으니 해당 명령어를 npm scripts에 추가하도록 합시다.
- pakage.json
"main": "./5차시(1)JavsScript/server.js", ... "scripts": { "server" : "nodemon server.js", "test": "echo \"Error: no test specified\" && exit 1" },
(main 실행 경로도 실습 폴더의 server로 잘 맞춰야 합니다)
npm run server
이제 터미널에 위와 같이 입력하면 자동으로 nodemon server.js 명령어가 실행됩니다.
8. 경로 요청에 따라 특정 응답 보내기
server.js 에서 get() 요청을 통해 res.end(<h1>Hello Node.js world!</h1>) 과 같은 응답을 보여줬습니다.
Express 서버가 HTTP 요청을 받으면 응답(response)를 반환하게 됩니다. 이 응답 객체를 이용해 클라이언트에게 응답을 하게 되는데, 이때 응답을 하는 여러가지 방법이 있습니다. 대표적으로 아래 세 가지를 비교해봅시다.
- res.send(): 정보를 돌려보내는 가장 기본적인 역할을 수행한다. Buffer, String, Object, Array가 들어갈 수 있다.
- 인자 타입에 따라 자동으로 Content-Type을 설정한다.
- Buffer - "application/octet-stream"
- String - "text/html"
- Array/Object - "application/json"
- 데이터를 돌려보내고 마지막에 세션을 종료한다.
- 인자 타입에 따라 자동으로 Content-Type을 설정한다.
- res.end(): 주로 서버가 작동을 안 하거나 오류가 있을 경우 특정 문구와 함께 응답을 종료하고자 사용 된다.
- res.json(): 정보를 JSON 형태로 바꿔서 돌려준다. 응답 헤더를 "application/json"으로 설정한다.
그렇다면 특정 html 파일을 보여주기 위해선 어떻게 해야할까요? (SSR)
바로 sendFile()을 사용하는 것입니다.
- server.js
app.get("/", (req, res)=> { // res.end("<h1>Hello Node.js world!</h1>"); res.sendFile(__dirname + '/public/js01/index.html'); })
- index.html
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="index.css"> </head> <body> <h1>제발 돼라</h1> </body> </html>
- index.css
body { background-color: pink; }
그런데 아래와 같은 실행 결과를 보면 어딘가 이상하다.
link 로 연결한 css 파일이 적용 되지 않았습니다.
그 이유는 sendFile로 보낸 html 단일 파일만 브라우저가 가져왔기 때문이다.
이를 해결하기 위해선... 정적 파일을 처리해야 합니다.
9. Express 정적 파일 처리하기
- 정적(static)파일이란?
- 직접 값이나 데이터에 변화를 주지 않는 이상 변하지 않는 파일.
웹 서비스를 제공하기 위해선 CSS, JS, HTML, 이미지 등 정적 파일들이 필요합니다.
그러나 웹 브라우저는 이것들을 각각 따로 서버에 요청해서 가져올 줄만 알죠.
우리가 원하는 화면은 위 정적 파일들이 한번에 합쳐진 모습입니다.
=> 그러니 정적 파일들을 특정 폴더에 모아두고 URL로 바로 접근해야 합니다. 외부에서 접근 가능한 파일을 모아둔 폴더가 되는 겁니다. 이를 수행하기 위해 Express 모듈은 express.static 이라는 미들웨어* 함수를 제공합니다.
- 미들웨어(Middleware)란?
- 요청과 응답 중간에서 어떤 동작을 하는 프로그램(함수).
Express는 요청이 들어오면 그에 대한 응답을 보내줍니다. 이때 클라이언트가 보낸 요청을 받고 응답하는 중간(middle) 과정에서 개발자가 개입하여 요청/응답 객체에 대한 변경과 흐름 제어를 합니다.
Express는 사실 거의 모든 게 미들웨어라고 하네요. 요청과 응답을 조작해서 기능을 추가하니까 당연한 말 같습니다.
미들웨어는 아래와 같이 사용 합니다.
app.use(미들웨어) // 모든 요청에서 미들웨어 실행 app.use(미들웨어 사용 경로 `/user`, 미들웨어) // user로 시작하는 모든 요청에서 미들웨어 실행 app.METHOD(미들웨어) app.METHOD(미들웨어 사용 경로, 미들웨어)
참고로 상단에 놓인 미들웨어가 먼저 실행되며 하단에 있을 수록 나중에 실행 됩니다.
더 자세한 예시는 아래와 같습니다.
// 모든 요청에 대해 미들웨어 실행 app.use((req, res, next) => { console.log('미들웨어입니다.'); next(); }); // GET 요청에서 미들웨어 실행 app.get('/', (req, res, next) => { res.sendFile(path.join(__dirname, '/index.html')); next(); }, (req, res) => { throw new Error('에러 발생'); });
next() 호출을 통해 다음 미들웨어가 실행 됩니다.
res.send(), res.json() 과 같이 응답 스트림을 종료하고 응답을 보내는 경우에는 사용하지 않습니다.
다만 응답이 없는 함수에서는 반드시 호출하여 다음 미들웨어 함수에 제어를 전달해야 한다. 그렇지 않으면 해당 요청은 정지된 채 방치됩니다.
- 그래서 static 파일 처리는 어떻게 하는데?
우선 정적 파일들(html, css, js, images)를 담을 public 폴더를 생성합니다.
저는 아래와 같이 만들었습니다.
📁public
└ index.html
└ style.css
└ main.js이제 static 파일을 호출할 폴더 경로를 지정합니다. 방법은 아래와 같이 다양합니다.
// 1. root 경로의 폴더명 'public' app.use(express.static('public')) // root 경로의 폴더명 'public' // 2. ('요청 경로', express.static(폴더 상대경로) app.use('/', express.static("./public")) // 3. 절대 경로로 지정 app.use('/', express.static(path.join(__dirname, '/public')))
- root 폴더의 바로 아래에 있는 public 폴더를 정적 파일을 담은 폴더로 지정한다.
- 기본 경로 '/' 을 요청했을 때, Express는 public 폴더 안에 있는 해당 경로의 파일을 찾는다.
- app.use('/static', express.static('upload')); 이라면
- /static 가상 경로로 접근하여 upload 폴더의 정적 파일을 load 한다.
- http://localhost:3000/static/css/style.css 와 같이 접근할 수 있는 것이다.
- 절대 경로로 지정하고 싶을 때 사용한다.
- __filename: 현재 실행중인 "파일" 경로
- __dirname: 현재 실행중인 "폴더" 경로
이렇게 설정하고 나면,
http://localhost:3000/static/index.html
http://localhost:3000/static/style.css
http://localhost:3000/static/main.js와 같은 경로로 public 폴더 내에 있는 정적 파일들을 외부에서 접근할 수 있게 됩니다.
이때 /static 가상 경로로 접근 이라는 게 뭐냐면, 실제 서버의 폴더 경로는 public/index.html 이더라도, 요청 주소에는 /static 이라고 기입하여 실제 경로를 나타내지 않는 것입니다.
이는 외부인이 서버의 구조를 쉽게 파악할 수 없도록 하기 위해 사용합니다.
참고로 요청 경로에 해당하는 파일이 없으면 내부적으로 알아서 next() 를 호출합니다.
그러나 파일을 발견하면 그 다음 미들웨어는 실행되지 않습니다. 응답으로 파일을 보내고 next()를 호출해야 합니다.
결론적으로,
- 나의 파일 구조
- app.js 에 이렇게 파일 경로를 설정
const express = require('express'); const cors = require('cors'); const path = require('path'); class App { constructor() { this.app = express(); this.setStatic(); } setStatic() { this.app.use('/', express.static(path.join(__dirname, '/public'))); } } module.exports = new App().app;
- 결과
외부에서 css파일 접근에 성공함을 알 수 있습니다!
'Studying > JavaScript' 카테고리의 다른 글
[TypeScript] enum+union으로 문자열 타입 상수화하기 (0) 2023.07.14 dangerouslySetInnerHTML 의 보안에 대하여 (0) 2023.07.13 공공 코로나 데이터 OpenAPI 연결 (0) 2022.08.30 [Socket.io] 웹소켓으로 그림판 채팅 만들기 (0) 2022.08.10 [Ajax] 중고차 홈페이지 만들기 (0) 2022.07.30