1. 요청과 응답
server1.js
const http = require('http');
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8'});
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server!</p>');
})
.listen(8080, () => {
console.log('8080번 포트에서 서버 대기 중입니다!');
});
res 객체
- res.writeHead : 응답에 대한 정보를 기록하는 메서드이다. 첫 번재 인수로 성공 요청임을 의미하는 200, 두번째 인수로는 응답에 대한 정보를 보내는데, 콘텐츠 형식이 HTML임을 알린다. 이부분을 헤더라고 한다
- res.write : 첫번째 인수는 클라이언트로 보낼 데이터다. 데이터가 기록되는 부분을 본문 이라고 한다.
- res.end : 응답을 종료하는 메서드
listen 메서드에 콜백 함수를 넣는 대신, 다음과 같이 서버에 listening 이벤트 리스너를 붙여도 된다.
server1-1.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8'});
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server1-1!</p>');
});
server.listen(8080);
server.on('listening', () => {
console.log('8080번 포트에서 서버 대기 중입니다!');
});
server.on('error', (error) => {
console.error(error);
});
res.write와 res.end에 일일이 HTML을 적는 것은 비효율적이므로 미리 HTML파일을 만들어두는 것이 바람직하다.
HTML파일은 fs모듈로 읽어서 전송할 수 있다.
server2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Node.js 웹 서버</title>
</head>
<body>
<h1>Node.js 웹 서버</h1>
<p>만들 준비 되셨나요?</p>
</body>
</html>
server2.js
const http = require('http');
const fs = require('fs').promises;
http.createServer(async (req, res) => {
try{
const data = await fs.readFile('./ch04/server2.html');
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(data);
} catch (err){
console.error(err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message);
}
})
.listen(8081, () => {
console.log('8081번 포트에서 서버 대기 중입니다!');
})
요청이 들어오면 먼저 fs모듈로 HTML 파일을 읽는다. data 변수에 저장된 버퍼를 그대로 클라이언트에 보내면 된다.
이번 예제까지는 모든 요청에 한가지 요청을 해봤다. 요청별로 다른 응답을 하는 방법에 대해서 알아보자
2. REST와 라우팅 사용하기
REST : REpresentational State Transfer 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법
RESTful : REST를 따르는 서버를 'RESTful하다'라고 표현한다
HTTP 요청 메서드
GET : 서버 자원을 가져오라고 할 때 사용
POST : 서버에 새로 등록하고자 할 때 사용(또는 뭘 써야할 지 애매할 때)
PUT : 서버의 자원을 요청에 들어있는 자원으로 치환하고자 할 때 사용 (전체 수정)
PATCH : 서버 자원의 일부만 수정하고자 할 때 사용 (부분 수정)
DELETE : 서버의 자원을 삭제하고 할 때 사용
REST에 기반한 서버 주소 구조
HTTP 메서드 | 주소 | 역할 |
GET | / | restFront.html 파일 제공 |
/about | about.html 파일 제공 | |
/users | 사용자 목록 제공 | |
기타 | 기타 정적 파일 제공 | |
POST | /user | 사용자 등록 |
PUT | /user/사용자id | 해당 id 사용자 수정 |
DELETE | /user/사용자id | 해당 id 사용자 제거 |
restFront.css
a { color: blue; text-decoration: none; }
restFront.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<title>RESTful SERVER</title>
<link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div>
<form id="form">
<input type="text" id="username">
<button type="submit">등록</button>
</form>
</div>
<div id="list"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./restFront.js"></script>
</body>
</html>
restFront.js
async function getUser() { // 로딩 시 사용자 가져오는 함수
try {
const res = await axios.get('/users');
const users = res.data;
const list = document.getElementById('list');
list.innerHTML = '';
// 사용자마다 반복적으로 화면 표시 및 이벤트 연결
Object.keys(users).map(function (key) {
const userDiv = document.createElement('div');
const span = document.createElement('span');
span.textContent = users[key];
const edit = document.createElement('button');
edit.textContent = '수정';
edit.addEventListener('click', async () => { // 수정 버튼 클릭
const name = prompt('바꿀 이름을 입력하세요');
if (!name) {
return alert('이름을 반드시 입력하셔야 합니다');
}
try {
await axios.put('/user/' + key, { name });
getUser();
} catch (err) {
console.error(err);
}
});
const remove = document.createElement('button');
remove.textContent = '삭제';
remove.addEventListener('click', async () => { // 삭제 버튼 클릭
try {
await axios.delete('/user/' + key);
getUser();
} catch (err) {
console.error(err);
}
});
userDiv.appendChild(span);
userDiv.appendChild(edit);
userDiv.appendChild(remove);
list.appendChild(userDiv);
console.log(res.data);
});
} catch (err) {
console.error(err);
}
}
window.onload = getUser; // 화면 로딩 시 getUser 호출
// 폼 제출(submit) 시 실행
document.getElementById('form').addEventListener('submit', async (e) => {
e.preventDefault();
const name = e.target.username.value;
if (!name) {
return alert('이름을 입력하세요');
}
try {
await axios.post('/user', { name });
getUser();
} catch (err) {
console.error(err);
}
e.target.username.value = '';
});
about.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>RESTful SERVER</title>
<link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<div>
<h2>소개 페이지입니다.</h2>
<p>사용자 이름을 등록하세요!</p>
</div>
</body>
</html>
restServer.js
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const users = {}; // 데이터 저장용
http.createServer(async (req, res) => {
try {
console.log(req.method, req.url);
if (req.method === 'GET') {
if (req.url === '/') {
const data = await fs.readFile(path.join(__dirname, 'restFront.html'));
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(data);
} else if (req.url === '/about') {
const data = await fs.readFile(path.join(__dirname, 'about.html'));
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(data);
} else if (req.url === '/users') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
return res.end(JSON.stringify(users));
}
// /도 /about도 /users도 아니면
try {
const data = await fs.readFile(path.join(__dirname, req.url));
return res.end(data);
} catch (err) {
// 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
}
} else if (req.method === 'POST') {
if (req.url === '/user') {
let body = '';
// 요청의 body를 stream 형식으로 받음
req.on('data', (data) => {
body += data;
});
// 요청의 body를 다 받은 후 실행됨
return req.on('end', () => {
console.log('POST 본문(Body):', body);
const { name } = JSON.parse(body);
const id = Date.now();
users[id] = name;
res.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('등록 성공');
});
}
} else if (req.method === 'PUT') {
if (req.url.startsWith('/user/')) {
const key = req.url.split('/')[2];
let body = '';
req.on('data', (data) => {
body += data;
});
return req.on('end', () => {
console.log('PUT 본문(Body):', body);
users[key] = JSON.parse(body).name;
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end(JSON.stringify(users));
});
}
} else if (req.method === 'DELETE') {
if (req.url.startsWith('/user/')) {
const key = req.url.split('/')[2];
delete users[key];
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end(JSON.stringify(users));
}
}
res.writeHead(404);
return res.end('NOT FOUND');
} catch (err) {
console.error(err);
res.writable(500);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(err);
}
})
.listen(8082, () => {
console.log('8082번 포트에서 서버 대기 중입니다');
});
req.method로 HTTP 요청 메서드를 구분한다.
메서드가 GET이면 다시 req.url로 요청 주소를 구분한다.
주소가 /일 때는 restFront.html을 제공하고, 주소가 /about이면 about.html파일을 제공한다. 이외의 경우는 주소에 적힌 파일을 제공한다.
만약 존재하지 않은 파일을 요청하거나 GET메서드요청이 아니라면 404에러가 응답으로 전송되고, 응답 과정 중에 예기치 못한 에러가 발생하는 경우 500에러가 응답한다.
POST /user 요청에서는 사용자를 새로 저장하고 있으며, PUT /user/아이디 요청에서는 해당 아이디의 사용자 데이터를 수정하고, DELETE /user/아이디 요청에서는 해당 아이디의 사용자를 제거한다.
POST와 PUT 요청을 처리할 때 req.on('data')와 req.on('end')는 요청에 본문에 들어있는 데이터를 꺼내기 위한 작업이라고 보면 된다.
서버를 실행하고 (http://localhost:8082/)로 접속한다
Network 탭에서 네트워크 요청 내용을 실시간으로 볼 수 있다.
Name(요청 주소), Method(요청 메서드), Status(HTTP 응답코드), Protocol(통신 프로토콜), Type(요청의 종류), xhr(AJAX 요청)
참고 : ⌜node.js 교과서⌟ 책을 공부하며 요약・정리한 내용입니다.
'💻 Web_Back end > node.js' 카테고리의 다른 글
[node.js] npm (0) | 2023.05.11 |
---|---|
[node.js] 쿠키와 세션 (0) | 2023.05.10 |
노드 파일 시스템 접근하기 (0) | 2023.05.09 |
노드 기능2 (내장 객체, 내장 모듈) (1) | 2023.05.08 |
노드 기능1 (모듈로 만들기) (0) | 2023.05.08 |