-
[멋쟁이사자차럼] 프론트엔드 스쿨 7기 - 37일차 기록 및 복습 (node, express)Front-end 개발 2023. 8. 30. 12:00
목차
1. Node.js
2. Node.js 설치
3. JavaScript 문법
4. API 서버 구축
5. npm과 package 설치
6. middleware
7. express-session
멋사 프론트엔드 스쿨은 백엔드 내용은 거의 다루지 않지만,
정규 커리쿨럼 LMS 수업에 Node.js (녹화)강의가 들어가 있다.덕분에 궁금했던 서버 생성 및 로그인 기능을 구현해 볼 수 있는 시간을 가졌다.
이를 이용하면 좀 더 완성도 있는 프론트엔드 프로젝트 개발이 가능할 것이다.
1. Node.js 란?
1-1. Node.js 개념
- Node.js 는 JavaScript 런타임이다. Node.js 는 이벤트 기반, 논 블로킹 I/O 모델을 사용해 가볍고 효율적이다.
- 자바스크립트 런타임 : 프로그래밍 언어가 구동되는 환경
- 자바스크립트 구동환경은 node.js 가 등장하기 이전에는 웹 브라우저에 한정되었다. 하지만 node.js가 등장하면서 서버, 머신러닝, 데스크탑 앱 등에서 자바스크립트를 활용할 수 있게 되었다.
- 자바스크립트를 이용할 수 있는 분야가 넓어졌다.
- 이벤트 기반이란 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식
- 이벤트 기반 시스템의 구성 요소로는 호출 스택, 백그라운드, 테스크 큐, 이벤트 루프가 있다.
(1) 실행 순서대로 호출 스택에 쌓기
(2) 백그라운드가 필요한 작업은 백그라운드로 이동
(3) 백그라운드에서 작업이 완료되면 태스크 큐로 이동
(4) 호출 스택들이 다 완료되면 이벤트 루프가 테스크 큐 작업을 호출 스택으로 이동
- 논 블로킹 I/O란
(1) 이전 작업이 완료될 때까지 기다리지 않고, 다음 작업들을 수행
(2) 오래걸리는 작업은 백그라운드에서 처리
- 반대로 블로킹 I/O 는 서버와 클라이언트가 있을 경우, 작업이 완료될 때가지 클라이언트가 아무런 작업을 하지 못하고 서버를 기다리게 된다. 작업시간이 3시간이라면 3시간 동안 클라이언트가 새로운 요청을 보내도 서버는 이를 처리할 수 없게 된다.
- 논 블로킹 I/O 모델은 클라이언트는 서버가 처리를 하고 있는 중에도 요청을 보낼 수 있고, 서버에서는 요청이 완료되는 즉시 응답을 받을 수 있는 구조를 가지고 있다. 그렇기 때문에 기다리지 않고 요청과 응답을 받을 수 있다는 점이 논 블로킹 I/O 모델의 큰 장점이다.
- 싱글 스레드란? 주어진 작업을 한개의 스레드가 처리하는 방식.
- 스레드란? 작업을 실행하는 단위.
- 싱글 스레드 Vs 멀티 스레드
- 싱글 스레드의 특징
(1) 주어진 작업을 혼자 처리하는 방식(2) 스레드에 문제가 생길 시 전체에 문제가 생길 가능성 있음
(3) 메모리나 기타 자원을 효율적으로 사용 가능
- 멀티 스레드의 특징
(1) 여러개의 스레드가 일을 나눠 처리
(2) 하나의 스레드가 문제 생겨도 다른 스레드로 대체 가능 (안정성이 높음)
(3) 스레드간의 작업 전환 비용, 놀고 있는 스레드 문제 발생
1-2. Node.js 의 장단점
- 장점
(1) 싱글 스레드, 논 블로킹 I/O에 따른 빠른 속도
(2) 컴퓨터 자원을 덜 사용
(3) I/O 작업이 많은 서버로 적합(채팅, 스트리밍)
(4) JavaScript 를 사용하기에 프론트엔드 개발자 사용 용이
(5) 생산성이 높음
- 단점
(1) 싱글 스레드 기반이라 자원을 많이 먹는 작업이 오면 부하가 크게 걸림
(2) 싱글 스레드는 CPU 코어 한개만 사용 => CPU 작업 많은 서버로 부적합...(게임서버등)
(3) 로직이 복잡한 경우 콜백함수의 늪에 빠질 수 있음
(4) 에러가 발생하면 프로세스 자체가 죽어버림
- Node.js 가 어울리는 서비스 (Netflix, PayPal, LinkedIn, Facebook)
(1) 간단한 로직으로 구성된 서비스
(2) 빠른 응답시간이 요구되는 서비스
(3) 빠른 개발이 요구되는 서비스
(4) 비동기방식에 어울리는 서비스 (채팅, 스트리밍 서비스)
2. Node.js 설치
2-1. 공식 홈페이지에서 자신의 OS 맞게 설치
- OS 에 맞는 설치 파일을 다운받아 설치한다.
- LTS (Long Tern S) 는 안정적이고 신뢰성이 있기 때문에 선택한다.
- 최신 기능의 경우 안정성이 떨어지는 위험요소가 있다.
2-2. VDCode 를 자신의 OS 맞게 설치
- Stable 버전의 설치 파일을 다운받아 설치한다.
- node.js 설치 확인
node --version // 설치된 Node.js 버전 npm --version // 함께 자동 설치된 npm 버전
2-3. VSCode Extension 설치
- VSCode 한국어 팩 : Korean Language Pack for Visual Studio Code
> 실행방법 : 명령 팔레트(Ctrl + Shift + P)에서 'Configure Display Language' 를 검색하여 클릭 후 표시되는 언어 중 한국어(ko)를 클릭
- Express 를 사용할 때 HTML 코드를 자동 완성 시켜주는 확장팩 : ejs Snippets
- 폴더나 파일에 맞게 파일의 아이콘이 바뀌어 파일 구분 인식에 도움을 준다 : Mterial Icon Theme
3. JavaScript 문법
3-1. var
- var 는 function scope 를 가지고 있는 변수 선언 방식이다.
> Scope 란 변수에 접근할 수 있는 범위
- var.js 파일 생성하고 콘솔로그 찍기
var hello = "hello"; function sayHello(){ var hello = "hello hello"; console.log(hello); } sayHello(); // hello hello console.log(hello); // hello
- node 로 js 파일 실행 시키기
node var.js // hello hello // hello
- function scope 가 아닌 곳에서 var 변수 선언을 하면 아무런 오류가 발생하지 않고, 해당 변수를 다시 초기화 한다.
- 유연한 var 변수 선언이 JavaScript 의 특징이다.
- 하지만 유연한 변수 선언이 오히려 단점이 될 수 있다.
- 변수를 두 번 초기화 하는 것은 잘못된 코드를 작성하기 쉽기 때문에 지양해야 한다.
- 그렇기 때문에 ES6에 와서는 새로운 변수 선언 방식인 let, const 가 등장했다.
3-2. let, const
- let 은 값을 재정의 할 수 있는 변수 선언 방식 ( => 변수, 변하는 수)
- const 는 값을 재정의 할 수 없는 변수 선언 방식 ( => 상수, 변하지 않는 수)
- var 와 let 의 차이점
(1) let은 같은 이름의 변수를 중복해서 선언할 수 없다.
(2) let은 function scope 가 아닌 중괄호 스코프를 가지고 있다.
- const 특징
(1) 한번 정의된 이후에는 다시 초기화할 수 없다. (바뀔 필요가 없는 값에 사용)
(2) let과 동일하게 중괄호 스코프를 가지고 있다.
- var 에서는 잘못된 코드가 잘성될 수 있기 때문에 ES6 이후로는 주로 let 과 const 만을 사용한다.
3-3. Arrrow Function
- ES6 이후 Arrow Function (화살표 함수) 이 나왔다.
- 화살표 함수는 function 키워드 보다 간단히 함수 표현이 가능하다.
- 화살표 함수는 매개변수가 하나일 경우에는 소괄호를 생략 가능하고 리턴값만 있다면 중괄호를 생략할 수 있다.
const foo = x => x; console.log(foo("arrow")); // arrow const bar = (x,y) => { console.log("2줄 이상"); return x + y; } console.log(bar(1,5)); // 2줄 이상 // 6
3-4. 비구조화 할당
- 비구조화 할당이란 객체, 배열안의 값을 추출해서 변수, 상수에 바로 선언하는 문법
- 객체 안의 값으로 변수 선언을 하고자 할 때, 비구조화 할당을 활용하면 일일이 변수 선언을 할 필요가 없다.
const object = { a:1, b:2 }; // const a = object.a; // const b = object.b; const {a,b} = object; console.log(a); console.log(b); const array = [1,2]; const [one, two] = array; console.log(one); console.log(two);
3-5. 비동기
- 자바스크립트의 가장 큰 특징은 비동기.
- 비동기란 태스크, 코드의 흐름을 기다리지 않고, 각자 수행한 다음 결과 값을 끝나는 대로 알려주는 방식
- 비동기는 1, 2, 3 모두 동시에 작동을 하고 먼저 끝나는 대로 출력한다. 이러한 비동기적 특성 때문에 각각의 태스크가 언제 끝나는지 모를 수 있는 상황을 맞이할 수 있다. 순차적인 동작이 필요한 경우 비동기적인 특성으로 인해 순차적인 흐름이 보장될 수 없는 문제가 발생한다. 그렇기 때문에 자바스크립트에서는 callback 함수 방식을 활용하고 있다.
- callback 함수는 함수가 실행한 뒤에 다음 함수가 이어서 실행되며 일련적인 흐름을 만드는 함수 실행 방식
- 하지만 callback 함수를 남발해서 사용하게 되다 보면 callback hell 이 발생할 수 있다.
- callback hell 을 탈출하기 위해 탄생한 새로운 문법이 바로 promise 이다.
3-6. promise
- promise 는 세 가지 상태 (pending, fulfilled, rejected) 를 가진다.
(1) pending (대기) : 비동기 처리 로직이 아직 완료되지 않은 상태
(2) fulfilled (이행) : 비동기 처리가 완료되어 프로미스가 결과 값을 반환해준 상태(3) reject (실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
- promise 를 생성하고 성공(resolve) 인 경우 코드는 아래와 같다.
function sayHello() { return new Promise((resolve, reject) => { const hello = "Hello Hello"; resolve(hello); }); } sayHello().then((resolvedData) => { console.log(resolvedData); }); // Hello Hello
- promise 를 생성하고 실패(reject) 인 경우 코드는 아래와 같다.
function sayHello() { return new Promise((resolve, reject) => { // const hello = "Hello Hello"; // resolve(hello); reject(new Error()); }); } sayHello() .then((resolvedData) => { console.log(resolvedData); }) .catch((error) => { console.log(error); }); // Error
- 로그인과 같은 다양하고 순차적인 처리 과정이 필요한 경우 then 을 연속적으로 사용하는 방법은 아래와 같다.
function sayHello() { return new Promise((resolve, reject) => { // const hello = "Hello Hello"; // resolve(hello); // reject(new Error()); resolve("hello!!"); }); } sayHello() .then((resolvedData) => { console.log(resolvedData); return resolvedData; }) .then((resolvedData) => { console.log(resolvedData); return resolvedData; }) .then((resolvedData) => { console.log(resolvedData); }) .catch((error) => { console.log(error); }); // hello!! // hello!! // hello!!
- then 을 많이 쓰게 되면 then 자체도 복잡해질 수 있다.
- 또한 .then 을 사용하는 문법은 기존에 사용하던 코드 스타일이 아니기 때문에 작성 및 읽기가 불편하다.
- 이를 해결하기 위해 나온 것이 async/await 이다.
3-7. async/await
- promise 자체는 비동기적으로 움직이기 때문에, async/await 을 명시해줌으로써 아래 코드들이 실행되지 않고 기다리도록 한다. await 뒤에 나오는 함수가 실행한 후에 작동하도록 하겠다. await 을 쓰겠다고 함수 앞에 명시해주는 것이 async 키워드이다.
- async/await 를 사용하면 promise 문법을 사용하지 않고도 promise 의 리턴값을 정상적으로 가져올 수 있다.
- async/await 의 장점으로는 익숙한 형태로 코드 스타일링을 할 수 있다는 것이다.
- .then 의 경우 자바스크립에 특화된 문법이며, async/aswait 를 적용하면 일반적인 코드 작성을 따르기 때문에 훨씬 코드 가독성이 올라간다.
function sayHello() { return new Promise((resolve, reject) => { resolve("hello!!"); }); } async function test() { const hello1 = await sayHello(); console.log(hello1); } test(); // hello!!
4. API Server 구축
4-1. Server
- 서버란 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 혹은 프로그램
- 웹 사이트에 접속했을 때, 이때 사용자는 클라이언트이며 URL 을 통해서 서버에 요청을 하고 응답을 받는다.
- 서버는 클라이언트의 요청에 대한 응답을 하는 역할
4-2. Node.js 로 Server 만들기
- writeHead 에 200 의 이미: 응답을 잘 보냈을 때
- writeHead 에 400 의 이미: 응답이 정상적으로 이루어지지 않았음
- 서버(port: 3000)를 만드는 코드는 아래와 같다.
- 코드를 node.js 로 실행시키면 웹 브라우저에서 서버에 접속 가능하다. (localhost:3000)
const http = require("http"); http.createServer((req, res) => { res.writeHead(200, {"Content-type": "text/html"}); res.end("<p>Hello World~!!!</p>"); }).listen(3000, () => { console.log("3000번 포트 서버 접속 완료~!!"); });
- localhost(= 127.0.0.1) : 현재 컴퓨터의 내부 주소.
- 로컬호스트는 내부 컴퓨터에 테스트 목적으로 접속하는 주소
- 서버 개발을 하는 도중에 실제 배포를 해서 사용자에게 접근할 수 있도록 하는 것은 보안상으로도 문제가 있으며, 유지보수, 테스트 측면에서도 효율적이지 못하다. 그렇기 때문에 컴퓨터 내부 주소로 테스트를 많이 한다.
- 127.0.0.1 는 IP (Internet Protocol) 주소로 이를 통해서 우리는 서버에 접속할 수 있다.
- 포트(Port)는 서버 내의 프로세스를 구분하는 번호
- 서버의 다양한 기능들을 구분하여 접근할 수 있도록 분리해둔 개념
- 서버에서는 다양한 일을 처리한다. (HTTP, DB 등등)
- IP 번호를 가장 기본적인 주소로 두고 기능에 따라서 포트 번호로 구분하고, 클라이언트 요청(포트번호)에 맞는 기능을 클라이언트에 응답한다.
4-3. Postman
- Postman 이란 서버 개발 시 이를 테스트할 수 있는 툴
- 브라우저를 통해 서버에 접근할 수 있지만, 브라우저 자체가 서버를 테스트 용도로 만들어진 것이 아니기 때문에 서버에 한정적으로 접근할 수 밖에 없다. 하지만 Postman 의 다양한 기능을 서버에 이용할 수 있다.
- Postman 을 설치한다.
- 가장 많이 쓰는 Http method
(1) GET : 단순 페이지 열람
(2) POST : 게시물 업로드
(3) PUT : 업데이트
(4) DLETE : 삭제
- 클라이언트가 서버에 요청을 보낼 때, 요청에 속성에 맞는 http method 를 꼭 정의해주어야 한다.
- Postman 은 서버에 요청을 보내고, 그에 대한 응답 쉽게 확인할 수 있는 툴이다.
- 서버를 개발하고 테스트 하는데 Postman 을 주로 사용한다.
4-4. API 서버 구축
- API 서버란 요청을 받고 응답을 하는 서버
- URL 요청에 맞게 분기처리하는 것을 라우팅이라고 함
- 추후 express 를 통해서 분기처리를 쉽게 할 수 있다.
const http = require("http"); // 서버를 쉽게 구축할 수 있게 해주는 패키지 http .createServer((req,res) => { // 서버 생성 if (req.url === "/") { res.writeHead(200); res.end("main url"); } else if (req.url === "/upload") { res.writeHead(200); res.end("upload url"); } else if (req.url === "/delete") { res.writeHead(200); res.end("delete url"); } else { res.writeHead(404); res.end("Not found!!!"); } }) .listen(3000, () => { // 3000번 포트에서 대기하도록 함 console.log("3000번 포트 서버 접속 완료~!!") })
5. npm 과 package 설치
5-1. npm 소개
- 처음부터 모든 것을 개발한다는 것은 매우 비효율적이다.
- 앞서 API 서버를 만들었을 때 사용했던 require("http") 가 다른 개발자가 만들어 놓은 패키지를 사용한 것
const http = require("http");
- npm(node package manager) 는 Node.js 로 만들어진 패키지를 설치하고 관리해주는 툴
- 패키지를 설치하다 보면 100개 이상의 패키지를 운용하는 경우가 있는데 서로 의존되는 패키지들이 존재한다.
- 서로 의존된다는 의미는 한 패키지에서 필요한 기능이 다른 패키지에서도 존재하는 의존적인 관계를 갖는다.
- 그리고 패키지의 버전마다 기능이 다를 수 있는 문제가 있다. (3.1 버전에는 있지만 3.2 버전에서는 없을 수 있다.)
- 즉 특정 버전의 패키지가 필요할 수 있다.
- 설치한 패키지 버전을 관리해주고 의존되는 패키지들의 의존성까지 관리해주는 package.json 파일이 필요하다
- npm 을 통해서 package.json 파일을 생성해보자
mkdir npm_test cd npm_test npm init >> package name: (npm_test) npm_test >> version: (1.0.0) >> entry point: (index.js) >> test command: >> git repository: >> keywords: >> author: kim >> license: (ISC) >> Is this OK? (yes) npm run test
- package.json의 script 는 npm의 명령어를 넣을 수 있는 부분이다.
- package-lock.json 은 설치한 모든 패키지의 버전 정보를 찾는 파일이다.
- express 패키지 설치
npm install express
- node_modules 폴더는 설치한 패키지와 관련된 모든 의존성 패키지들을 관리한다. express 패키지 하나에 딸린 다양한 의존성 패키지들이 설치된 것을 확인할 수 있다. 그렇기 때문에 package.json 파일이 꼭 필요하다.
- nodemon 패키지 설치
- install 의 '-D' 옵션은 개발환경에서만 사용한다는 의미. 개발환경에서는 쓰이지만 나중에 배포할 때는 필요없기 때문에 포함하지 않도록 한다.
npm install -D nodemon
- 설치 옵션에 따라 package.json 의 작성 방식이 달라진다.
{ "name": "npm_test", "version": "1.0.0", "description": "npm test file", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "hello": "echo hello" }, "author": "kim", "license": "ISC", "dependencies": { "express": "^4.18.2" }, "devDependencies": { "nodemon": "^3.0.1" } }
- '-g' 옵션은 글로벌 옵션으로 패키지를 프로젝트 폴더 마다 설치해주는 것이 아닌 전체적으로 설치해줌으로써 한 번의 설치만으로 다양한 프로젝트에서 사용할 수 있다.
- express-generator 패키지를 설치한다.
npm install -g express-generator
5-2. express
- express 는 빠르고 간편한 웹 프레임워크이다.
- 프레임워크란 프로그램을 만들기 위한 기본 틀.
- 프레임워크는 체계적인 코드관리와 유지보수가 용이하기 때문에 많이 쓰인다.
- 자동차의 경우 몸체에 해당하는 프레임이 있기 때문에 바퀴, 문, 시트, 핸들등을 어디에 달 수 있을 지 알기 쉽고
- 각각의 요소에 맞는 것들을 자신의 입맛에 맞게 넣어주기만 하면 자동차를 빠르게 완성할 수 있다.
- 자동차의 예와 같이 프로그래밍에서 쉽게 코드를 완성할 수 있도록 도와주는 것이 프레임 워크이다.
- 기존방식과 express 의 차이
기존 express 1. req.url 파싱
2. req.method 확인
3. 쿼리문 파싱1. req.get('경로')
2. req.query 로 확인- 기존의 방식과 달리 express 프레임워크는 요청시 http 메소드를 바로 사용하여 확인이 가능하고, req.query 를 통해서 요청에 맞는 query 문을 확인할 수 있는 장점이 있다.
- express 를 좀 더 간편하게 만든 것이 express-generator 이다.
- express-generator 는 express 의 기본 구조(디렉토리)를 만들어 준다
(1) www : 서버를 실행하는 파일이며 포트 번호를 지정 가능
(2) public 폴더 : image, js, css 등 리소스가 올라감
(3) routes : 페이지 라우팅과 관련된 파일 저장하고 실제 서버 로직을 작성한다.
(4) views : 템플릿 파일. jade, ejs 등과 같은 확장자명의 파일들이 있다.
템플릿 파일은 '<%= %>' 형태로 HTML 코드 안에 JS 코드를 쓸 수 있는 기능을 갖는다.(5) app.js : 핵심적인 서버 역할을 하며 라우팅의 시작점
(6) package.json : 의존성 관리 및 버전 관리
- express-generator 를 통해서 프로젝트를 생성
# --ejs : 템플릿 파일의 타입 express --ejs first-project # 파일 이동 cd first-project # 의존성 패키지 설치 npm install # 프로젝트 실행 npm start # 프로젝트 종료: ctrl + c
- 템플릿 파일이 jade, ejs 두가지 타입이 있지만, ejs 파일이 HTML 구조와 유사한 점이 많아서 ejs 를 선호하는 편이다.
- 프로젝트의 views 폴더의 index.ejs 파일에서 welcome 을 Hello 로 변경해볼 수 있다.
- 이때 프로젝트를 실행하기 위해서는 VSCode 에서 Ctrl + c 로 실행 종료를 한 후 다시 npm start 로 실행해야 한다.
- 수정사항이 있을 때마다 서버를 종료하고 다시 실행하는 번거로움을 해소하기 위해 nodemon 을 사용한다.
5-3. nodemon
- nodemon 을 통해서 프로젝트 실행 (뒤의 경로는 package.json 의 시작 명령어 경로와 같다.)
nodemon ./bin/www
- nodemon 을 통해서 프로젝트를 실행하면 서버를 껐다가 켰다가 하는 번거로움을 없앨 수 있다.
- 이제 프로젝트의 변경사항이 있다면 반영하여 파일 저장 후, 접속 브라우저에서 새로 고침 시 변경사항이 반영된다.
5-4. Routing
- Routing 은 URL 또는 http 메소드 요청에 따라 처리를 해주는 것
- express-generator 로 만든 프로젝트의 routes 하위 index.js 를 변경하고 Postman 에서 결과를 확인해본다.
- 요청방식(get, post)이 다르면 Not Found 를 출력한다.
var express = require('express'); var router = express.Router(); /* GET home page. */ router.get("/main", (req,res) => { res.json({ message: "main success!!", }); }); module.exports = router;
- 라우팅의 기본적인 개념은 URL 과 http 메소드로 구분지을 수 있다.
- 그리고 URL로 접근했을 때 콜백함수로 나오는 요청(req)과 응답(res)에 대한 객체가 있다.
- 요청을 받고 싶은 경우 req 객체를 이용하고, 응답을 할 경우에 res 객체를 이용한다.
- request 는 보통 클라이언트에서 데이터를 넘겨줄 때, 서버측으로 받기 위해 사용된다.
- request 데이터 body 에 담겨서 온다.
- 코드를 수정해서 간단한 요청과 응답을 아래와 같이 구현해볼 수 있다.
- response 메소드에 .send() 와 대표적인 .json() 메소드가 있다.
- Postman 에서는 post 방식으로 간단한 json 데이터를 보낸다.
var express = require('express'); var router = express.Router(); /* GET home page. */ router.post("/main", (req,res) => { // console.log(req.body); const data = req.body.data; // res.send("문자열이응답됩니다!!"); res.json({ message: "json 응답", request: data }); }); module.exports = router;
- 또 response 에는 reder() 메소드가 있는데, views 폴더의 HTML 이 들어간 템플릿 파일을 넣어서 렌더링 해준다.
- render() 메소드에는 파일명만 넣고 ejs 확장자는 넣지 않는다.
- 또 views 폴더에 index.ejs 파일에 있는 <%= title %> 를 꼭 넣어줘야 하는 것으로 보이므로 같이 넣어준다.
var express = require('express'); var router = express.Router(); /* GET home page. */ router.post("/main", (req,res) => { const data = req.body.data; res.render("index", { title: 'Express' }); }); module.exports = router;
- Postman 에서 응답으로 받은 HTML 을 볼 수 있으며, preview 로 렌더링 시 화면을 볼 수 있다.
5-5. HTTP method
- 서버에 요청을 보내는 방법.
- 서버에 요청을 보낼 때, 요청에 어떠한 목적과 의도를 가지고 있는지 메소드를 통해서 나타내는 방식
- HTTP method 에는 크게 4 가지의 방식 (GET, POST, PUT, DELETE)이 있다.
- GET : 요청 받은 정보를 검색하여 응답 (Read)
- POST : 요청된 자원을 생성 (Create)
- PUT : 요청된 자원을 수정 (Update)
- DELETE : 요청된 자원을 삭제 (Delete)
- 목적에 맞게 해당 메소드를 잘 선택하는 것이 좋은 서버를 구성하기 위한 방법
var express = require('express'); var router = express.Router(); let arr = []; // GET method router.get('/read', (req,res) => { res.status(200).json({ message: "read success" }); }); // POST method router.post('/create', (req,res) => { // console.log(req.body); const {data} = req.body; // 비구조화 할당 arr.push(data); res.status(200).json({ message: "create success", result: arr, }) }) // PUT method // update/0 : 0번째 데이터를 수정 (id === 0) router.put('/update/:id', (req,res) =>{ const {id} = req.params; // console.log(id); const { data } = req.body; arr[id] = data; res.status(200).json({ message: "update sucess", result: arr, }); }); // DELETE method router.delete('/delete/:id', (req,res) => { const {id} = req.params; arr.splice(id,1); res.status(200).json({ message: "delete success", result: arr, }) }) module.exports = router;
- 데이터를 넣을 빈 배열을 하나 선하고 앞서 GET, POST 를 배웠던 것을 복습해보자
- POST 방식으로 저장한 배열의 데이터는 nodemon 에서 프로젝트 저장시 자동 초기화 시켜 사라진다.
- 다시 POST 방식을 데이터를 채우고, PUS 방식으로 배열의 데이터를 업데이트(수정) 할 수 있다.
- 다시 POST로 데이터를 넣고, DELETE 방식으로 지정 배열의 데이터를 지울 수 있다.
- body 의 데이터는 지워줘도 상관없다. 데이터를 보내면 POST 방식과 같이 req.body 에 데이터가 담긴다.
6. 미들웨어
6-1. 미들웨어 개념
- 미들웨어란 요청(request)이 들어오고 응답(response) 을 하기까지 사이에서 목적에 맞게 처리하는 함수들
- 미들웨어가 필요한 이유는 각각의 요청과 응답 사이 같은 작업을 따로 수행하지 않고 공동적인 작업을 수행한다.
- 미드웨어 작성은 app.js 파일에서 작업한다.
app.use((req, res, nest) => { console.log("middleware!!"); next(); });
- 위 코드는 어떤 요청이 들어와도 미들웨어 함수를 한번씩 실행하게 된다. next() 함수가 실행된 후에 라우터에 맞게 함수가 실행이 된다.
6-2. 로그인 체크 미들웨어 구현
- 미들웨어는 요청과 응답 사이에 위치해서 특정한 로직을 수행하는 함수
- routes 하위 index.js 파일에는 서버 로직만 만들어줘야 하기 때문에 외부 파일로 미들웨어 함수를 분리한다.
- 미들웨어 함수는 상위 module 파일을 만들어서 위치 시켜주고 module.export 를 해준다.
- 그리고 index.js 파일에서는 loginCheck 함수를 불러와서 사용한다.
// ~/module.loginCheck.js const loginCheck = ((req, res, next) => { // const userLogin = true; const userLogin = false; if (userLogin) { next(); } else { res.status(400).json({ message: "login fail!!", }); } }); module.exports = loginCheck;
// ~/routes/index.js var express = require('express'); var router = express.Router(); const loginCheck = require("../module/loginCheck"); router.get("/", loginCheck, (req, res) => { res.status(200).json({ message: "login success!!", }); }); module.exports = router;
6-3. multer
- 이미지 업로드 서버 구축
- multer 는 파일을 업로드할 때 유용한 패키지이다.
- upload 는 파일 업로드 방식을 upload 에 저장
- .single("image"); 는 파일 1개를 업로드하고 image 라는 FormData 를 전송한다.
upload.single("image");
- FormData 전송이란 Postman 에서 body type 중 하나인 form-data 이다.
- 실습에서는 key 를 image 로 전달할 것이다.
- 업로드 된 파일은 req.file 에서, 나머지 요청 데이터는 req.body 에서 불러와 사용할 수 있다.
- multer 설치
npm install multer
- multer 패키지 하위에 imageUpload.js 파일을 생성하고 미들웨어 함수를 구현한다.
const multer = require("multer"); const upload = multer({dest: "uploads/"}); module.exports = upload;
- routers 하위 index.js 에 서버 로직을 추가한다.
var express = require('express'); var router = express.Router(); const loginCheck = require("../module/loginCheck"); const upload = require("../module/imageUpload"); router.get("/", loginCheck, (req, res) => { res.status(200).json({ message: "login success!!", }); }); router.post('/upload', upload.single('image'), (req, res) =>{ const file = req.file; console.log(file); res.status(200).json({ message: "upload success!!", }); }); module.exports = router;
- Postman 에서 POST 방식으로 URL 을 수정하고 Body 데이터로 form-data 를 선택하여 Key 에 'image' 를 입력한다. 그리고 image 를 입력한 셀에 마우스를 올리면 셀의 오른쪽에 Text 라고 쓰게 되는데, 클릭하여 file 로 변경하여 Value 열의 select file 버튼을 클릭하여 올린다. 이미지 선택이 완료되면 send 버튼을 누른다.
- 이미지를 업로드하여 실행 중인 서버에 POST 로 날리면, 새로운 uplaods 파일이 생기며 이미지가 업로드 된다.
- 콘솔창에는 업로드된 이미지에 대한 정보와 위치가 표시된다.
- uploads 폴더속에 업로드 파일을 보면 압호화된 파일이 생성 됬음을 알 수 있다.
- 이후 작업으로는 public 폴더 안에 images 에 해당 파일을 넣어야 하며,
- 암호화된 파일명이 아닌 한눈에 알아보기 쉬운 파일명으로 변경해주어야 한다.
- uploads 폴더를 삭제한다.
- 업로드한 이미지 파일의 destination 과 filename 을 추가하도록 미들웨어를 수정한다.
const multer = require("multer"); // const upload = multer({dest: "uploads/"}); const storage = multer.diskStorage({ destination : (req, file, cb) => { cb(null, 'public/images/'); }, filename: (req, file, cb) => { cb(null, file.originalname); } }) const upload = multer({ storage : storage }); module.exports = upload;
6-4. ejs 개념
- ejs (Embedded Javascript Template) 는 템플릿 엔진
- 자바스크립트가 들어간 템플릿이라고 이해하면 된다.
- 템플릿 엔진이란 자바스크립트 안에 있는 코드를 views 파일 (HTML 코드) 안에서 사용할 수 있도록 만들어주는 엔진
- 모든 경우의 수에 따른 페이지를 구현해야하는 HTML 정적 웹 페이지는 한계가 분명하다.
- 이를 개선하기 위해 ejs 를 통해서 동적 웹 페이지가 만들어 졌다.
- 웹 페이지에 Javascript 변수나 코드를 넣어서 사용자에게 동적 정보를 전달하는 것이 템플릿 엔진이다.
- Javascript 변수를 view 파일 내에서도 사용 가능하도록 만들어준다.
- ejs 는 node.js 템플릿 엔진 중에서도 사용자가 가장 쉽게 익힐 수 있는 형태이기 때문에 사용하게 됬다.
6-5. ejs 3가지 문법 : (1) js 코드 : <% ... %>
- app.js 에 아래 코드를 적절한 곳에 추가한다. (catch 404 주석 위면 된다.)
// app.js const templateRouter = require('./routes/templates'); app.use('/template', templateRouter);
- routes 폴더에 template.js 파일을 생성하여 작성해준다.
const express = require("express"); const router = express.Router(); router.get('/ejs', (req, res) => { res.render("template"); }); module.exports = router;
- views 폴더에 template.ejs 파일을 만들어 사용자에게 보여줄 HTML 을 작성한다.
<!DOCTYPE html> <html> <head> </head> <body> <% if(true){ %> <div>ejs working</div> <% } else { %> <div>ejs not working</div> <% } %> </body> </html>
6-6. ejs 3가지 문법 : (2) 변수 출력 <%= ... %>
<!DOCTYPE html> <html> <head> </head> <body> <%= data %> </body> </html>
6-7. ejs 3가지 문법 : (3) ejs 분할 <% include 파일명 %>
- Navigation Bar 또는 Header 와 같이 반복되는 코드의 경우 외부로 파일을 분할하여 불러와서 효율적으로 사용한다.
- 브라우저에 http://localhost:3000/template/ejs 로 들어가서 결과를 확인한다.
<!DOCTYPE html> <html> <head> </head> <body> <% include ./layout/navbar.ejs %> <%= data %> </body> </html>
7. express-session
7-1. 로그인을 하는 상황
- 로그인을 할 경우 사용자는 서버에 username 과 password 를 보내게 된다.
- 서버는 'ok' 라고 보내게 되지만, 통신 특성상 상태를 저장하지 못한다.
- 로그인 상태를 저장하지 못하면 사용자는 로그인 이후 사용할 수 있는 기능을 사용하지 못하게 된다.
- 그래서 로그인 상태를 기억하는 기능이 필요하게 되는데, 이때 사용하는 것이 session 이다.
- session 은 저장이 필요한 정보를 서버에서 저장을 해서, 연결상태가 유지되도록 하는 기능이다.
- 이 처럼 session 은 상태 정보가 저장되지 않는 통신 특성의 한계를 극복하기 위해 만들어졌으며, 로그인과 같은 상황에서 자주 쓰인다.
- express-session 은 express 환경에서 session 을 조금 더 쉽고 빠르게 적용할 수 있도록 도와주는 패키지다.
7-2. express-session 설치
npm install express-session
7-3. 로그인 기능 구현하기
- app.js 에 session 추가
(1) secret 은 session 을 암호화 하기 필요한 값이다. secret 의 value 값은 임의로 작성해주면 된다.
(2) resave는 session 을 변경하지 않아도 저장할지를 결정하는 값이다. (false)
(3) saveUninitialized 는 session 이 저장되기 전에 이를 초기화해줄지를 결정해주는 값이다. (true)
const session = require("express-session"); var app = express(); app.use(session({ secret: "first project", resave: false, saveUninitialized: true, }));
- routes 폴더의 users.js 에 기존 코드를 지우고, login 기능 추가
- 추가적으로 username 과 PW 를 객체로 만들고, 입력값과 저장된 값이 존재 또는 일치하는지 확인하는 코드 추가
- session 을 받아오는 코드를 추가. 내부의render 는 index.ejs 를 사용자에게 보여주기 위함.
var express = require('express'); var router = express.Router(); /* GET users listing. */ // router.get('/', function(req, res, next) { // res.send('respond with a resource'); // }); const userInfo = { lee: { password: "123123", }, kim: { password: "456456", }, } router.get("/", (req, res) => { // 로그인 되어 있다면 값이 보내지고, 아니라면 null 반환 const session = req.session; res.render("index", { username: session.username, }); }); router.get('/login/:username/:password', (req,res) => { // username 저장 여부 확인 const session = req.session; const { username, password } = req.params; if(!userInfo[username]) { res.status(400).json({ message: "user not found", }); } if (userInfo[username]["password"] === password) { // pw 일치 여부 확인 session.username = username; res.status(200).json({ message: "user login!!", }); } else { res.status(400).json({ message: "user pw incorrect!!", }); } }); module.exports = router;
- 로그인 시에는 원래 POST 방식을 사용해야하지만 웹 페이지에서 동작 확인을 위해서 GET 메소드를 사용했다.
- 사용자에게 보여줄 index.ejs 수정
- 브라우저에서 아래 URL 로 접속하여 결과를 확인한다.
(1) http://localhost:3000/users
> 로그인이 필요합니다.(2) http://localhost:3000/users/login/lee/123123
> {"message":"user login!!"}(3) http://localhost:3000/users
> lee님 환영합니다.
<!DOCTYPE html> <html> <head> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <% if(username){ %> <div> <%= username %>님 환영합니다 </div> <% }else{ %> <div>로그인이 필요합니다</div> <% } %> </body> </html>
7-4. 로그아웃 기능 구현
- routes 폴더의 users.js 에 로그아웃 기능을 추가한다.
- req.session.destroy() 를 통해서 session 이 있다면 제거해준다.
- req.redirect() 는 지정해준 라우터로 이동시킨다.
router.get('/logout', (req,res) => { const session = req.session; if (session.username) { req.session.destroy((err) => { if (err) { console.log(err); } else { res.redirect("/users"); } }); } else { res.redirect('/users'); } });
- 브라우저에서 아래 URL 로 접속하여 결과를 확인한다.
(1) http://localhost:3000/users/logout
> 로그인이 필요합니다.
'Front-end 개발' 카테고리의 다른 글
[멋쟁이사자처럼] 프론트엔드 스쿨 7기 - D3, Chart.js 특강 (0) 2023.09.02 오늘의 복습 : 로또 번호 뽑기 (1) 2023.09.02 [책집필] 오리엔테이션 : 책 집필의 시작! (0) 2023.08.26 [책집필] 주제 선정 : 프론트엔드 개발자 기술 면접 (0) 2023.08.25 [멋쟁이사자처럼] 2023년 8월 KPT 회고 (0) 2023.08.25