EtoC
httpie 와 express 본문
nodejs를 사용하는 이유는 express를 통한 다양한 프레임워크를 사용할 수 있는점,
HTTP 서버를 쉽게 설정하고 관리할 수 있어서이다.
근데 express를 사용하지않고 순수 Node HTTP 모듈만을 사용해서 HTTP API를 만든다면 어떻길래?
궁금한건 못참지!
express를 사용하지않은 HTTP API
일단 폴더를 생성하고 npm init -y를 하여 패키지 제이슨을 만들었다.
const http = require("http");
const server = http.createServer((req, res) => {
console.log("request received");
res.setHeader("Content-Type", "application/json");
res.end(
JSON.stringify({ message: "Success to open http server without express!" })
);
});
server.listen(3000, "127.0.0.1", () => {
console.log("server is running on PORT 3000!");
});
- Node.js 내장 http모듈을 가져와서 사용할 수 있도록 변수에 담는다.
- http.createServer 메소드는 인자로 또 다른 함수를 받는다(콜백함수). 인자로 받은 함수의 첫번째 인자는 http request 의 정보가 담겨있는 객체이고, 두번째 인자는 http response 객체이다. 서버에 요청이 들어오면 이 인자로 받은 함수가 실행된다.
- 요청에 대한 응답의 header 를 application/json 형태로 세팅한다.
- res.end 함수를 통해서 요청에 대한 응답을 마무리 한다. 이 함수의 인자로 넘겨주는 값이 클라이언트가 받는 응답이 된다.
- server 는 앞서 생성한 서버를 의미하고 이 서버 객체의 listen 함수는 인자로 포트 번호와 콜백함수를 받는다. 포트번호로 서버를 연다는 의미이며, 서버가 실행될 때의 로직을 콜백함수 안에서 처리할 수 있다. 보통 서버가 켜져있다는 로그 메시지를 남긴다.
- Port 번호에 해당하는 3000은 Express 프레임워크에서 공식적으로 지정한 default Port 번호이지만 늘 고정된 값은 아니며 3000 이외의 번호로도 언제든지 서버를 열 수 있다.
- 터미널에 node js파일명을 입력하면 "server is running on PORT 3000!"라고 뜨며 서버가 실행된다.
- 브라우저를 열고 주소창에 localhost:3000에 접속하면 메세지가 뜬다?
서버 실행 성공이다.
브라우저 외에도 백쪽에서 클라이언트 툴을 사용해서 http sever에 요청을 보낼 수 있다.
예전에는 개발환경에서 httpie를 사용했다고하는데 요즘에는 다 postman을 사용하는거같다.
이번에는 옛날에 어떻게 개발했는지 궁금해서이니 httpie를 사용해봤다
httpie
httpie를 사용하기위해서는 우선 터미널 전용의 http client 프로그램이 설치되어있어야한다.
- MAC OS
brew install httpie
- Ubuntu 환경
apt-get install httpie 또는 sudo apt-get install httpie
설치 완료후 http localhost:3000을 입력하면 아래와같이 서버가 잘 작동하는지 확인 할 수 있다.
솔직히 여기까지하고 뭔가 성공했다는 메세지가뜨니 나는 뿌듯했다.
'할만하거같은데' 라고 생각했다.
하지만 express를 사용하지 않고 다음 코드를 작성하는것은 쉽지않았다.
백엔드는 유저의 회원가입, 로그인 같이 프론트측에서 오는 요청에대해 다양한 응답을 보내줘야한다.
이렇게 해당 자원에대해 다른 함수(로직)을 실행하도록 하는것을 라우팅(ROuting)이라한다.
express를 사용하지않고 만든 앱의 코드는 정말 끔찍했다.
일단 서버와 통신시 사용할 데이터를 변수에 담아서 코드에 추가하였다.
const users = [
{
id: 1,
name: "Rebekah Johnson",
email: "Glover12345@gmail.com",
password: "123qwe",
},
{
id: 2,
name: "Fabian Predovic",
email: "Connell29@gmail.com",
password: "password",
},
];
const posts = [
{
id: 1,
title: "간단한 HTTP API 개발 시작!",
content: "Node.js에 내장되어 있는 http 모듈을 사용해서 HTTP server를 구현.",
userId: 1,
},
{
id: 2,
title: "HTTP의 특성",
content: "Request/Response와 Stateless!!",
userId: 1,
},
];
그리고 request 객체에서 method와 url에따라 조건문으로 분기처리를 해서 라우팅을 진행한다.
이때 앱에서 회원가입과 게시물 확인같은 로직을 처리해 주어야한다.
라우팅은 http method에따라 적절한 로직으로 연결해준다.
이때 express를 사용하지않고 코드를 작성하면 어떻게 될까?
const http = require("http");
const server = http.createServer((request, response) => {
const { url, method } = request;
if (method === "GET") {
if (url === "/ping") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ message: "pong" }));
} else if (url === "/users") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ users: users }));
} else if (url.startsWith("/users")) {
const userId = parseInt(url.split("/")[2]);
const user = users.find((user) => user.id === userId);
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ user: user }));
} else if (url === "/posts") {
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify({ posts: posts }));
}
} else if (method === "POST") {
if (url === "/users") {
let body = "";
request.on("data", (data) => {
body += data;
});
request.on("end", () => {
const user = JSON.parse(body);
users.push({
id: user.id,
name: user.name,
email: user.email,
password: user.password,
});
response.end("ok");
});
} else if (url === "/posts") {
let body = "";
request.on("data", (data) => {
body += data;
});
request.on("end", () => {
const post = JSON.parse(body);
posts.push({
id: post.id,
name: post.title,
content: post.content,
});
response.end("ok");
});
}
} else if (method === "PATCH") {
if (url.startsWith("/posts")) {
let body = "";
request.on("data", (data) => {
body += data;
});
request.on("end", () => {
const inputPost = JSON.parse(body);
const postId = parseInt(url.split("/")[2]);
const post = posts.find((post) => post.id === postId)
post.title = inputPost.title;
post.content = inputPost.content;
response.writeHead(200, { "Content-Type": "application/json" });
response.end(
JSON.stringify({
id: post.id,
title: post.title,
content: post.content,
})
);
});
}
} else if (method === "DELETE") {
if (url.startsWith("/posts")) {
const postId = parseInt(url.split("/")[2]);
const post = posts.find((post) => post.id === postId);
delete post;
response.writeHead(204, { "Content-Type": "application/json" });
response.end(
JSON.stringify({
message: "NO_CONTENT",
})
);
}
}
});
server.listen(3000, "127.0.0.1", () {
console.log("Listening to requests on port 3000");
});
어떻게되긴 무수한 if else if문으로 인해 코드가 복잡해졌다
method 별로 조건문을 붙이고 url을 설정하니 너무 길다..
Express 적용
'Express is fast, unopinionated, minimalist web framework for node.js.'
'Express는 빠르고 자유롭고 가벼운 웹 프레임 워크이다.'
Express 는 Node 개발자들이 다수 채택하는 프레임워크로서, 앞서 언급한 라우팅 과 로직의 모듈화 를 위해 사용한다.
이는 개발자가 코드를 더 읽기 쉽도록 가독성을 높이고 지속가능하게 개발할 수 있게 도와준다.
const modularizedFunctions = require('./modularizedFunctions.js')
const express = require('express')
const app = express()
app.use(express.json())
//get 요청 처리 라우팅
app.get('/ping', (req, res) => {res.json({ message: '/pong'})})
app.get('/users', modularizedFunctions.getUsers)
app.get('/users/:userId', modularizedFunctions.getUserByUserId)
app.get('/posts', modularizedFunctions.getPosts)
//post 요청 처리 라우팅
app.post('/users', modularizedFunctions.createUser)
app.post('/posts', modularizedFunctions.createPost)
//patch 요청 처리 라우팅
app.patch('/posts/:postId', modularizedFunctions.updatePost)
//delete 요청 처리 라우팅
app.delete('/posts/:postId', modularizedFunctions.deletePost)
app.listen(3000, "127.0.0.1", function() {
console.log('listening on port 3000')
})
위의 코드는 레이어드패턴 아키텍처에 CRUD API를 사용한 코드이다.
express 프레임워크를 사용하니 어떤요청인지 요청 url이 어떻게 되어있는지도 한번에 볼수 있을정도로 코드가 간결해졌다.
그리고 if else if로 분기처리를 했던 코드들을 모듈화해서 사용할 수 있다.
// 서버통신시 가공할 데이터를 정의.
const users = [
{
id: 1,
name: "Rebekah Johnson",
email: "Glover12345@gmail.com",
password: "123qwe",
},
{
id: 2,
name: "Fabian Predovic",
email: "Connell29@gmail.com",
password: "password",
},
];
const posts = [
{
id: 1,
title: "간단한 HTTP API 개발 시작!",
content: "Node.js에 내장되어 있는 http 모듈을 사용해서 HTTP server를 구현.",
userId: 1,
},
{
id: 2,
title: "HTTP의 특성",
content: "Request/Response와 Stateless!!",
userId: 1,
},
];
// 앞서 express 없이 작성한 sendPosts 함수와 비교했을 때,
// express 덕분에 JSON.stringify 함수를 별도로 사용할 필요없이
// response 객체의 json 메소드를 활용하면 된다.
const getUsers = (req, res) => {
res.json({ users });
};
const getUserByUserId = (req, res) => {
const userId = req.params.userId;
const user = users.find((user) => user.id == userId);
res.json({ user });
};
const getPosts = (req, res) => {
res.json({ posts });
};
const createUser = (req, res) => {
const user = req.body;
const newUser = users.push({
id: user.id,
name: user.name,
email: user.email,
password: user.password,
});
res.json({ message: "created!", user_id: newUser });
};
const createPost = (req, res) => {
const post = req.body;
const newPost = posts.push({
id: post.id,
title: post.title,
content: post.content,
});
res.json({ message: "created!", post_id: newPost });
};
const updatePost = (req, res) => {
const inputPost = req.body;
const postId = req.params.postId;
const post = posts.find((post) => post.id == postId);
post.title = inputPost.title;
post.content = inputPost.content;
res.json({ message: "updated!", updatedPost: post });
};
const deletePost = (req, res) => {
const postId = req.params.postId;
const indexOfPostId = posts.findIndex((post) => post.id == postId);
delete posts[indexOfPostId];
res.json({ message: "deleted!" });
};
// serverWithExpress.js 에서 사용하기 위해 모듈로 내보낸다.
module.exports = {
getUsers,
getUserByUserId,
getPosts,
createUser,
createPost,
updatePost,
deletePost,
};
Express를 적용하기 전/후의 가장 큰 차이점은 바로 라우팅의 분리, 로직의 모듈화 였다.
Express를 이용하여 구현한 코드의 경우에는 기능별로 별도의 파일로 관리할 수 있다는 사실을 확인할 수 있었고,
이후 더욱 복잡해질 디자인/아키텍쳐 패턴을 적용하는데 기본 원리로 적용 될 것이다.