드디어
백엔드의 꽃 서버!!!!
이번에는 노드에서 서버를 구축하는 방식을 알아보자~
서버 구축의 방식에 있어서 이번에는 총 2가지의 방식을 비교해 보면서 그 과정을 살펴보겠다.
먼저, http 모듈이다. http모듈은 Node.js에서 가장 기본적이고 중요한 웹 모듈이다. 웹서버와 클라이언트 생성등 관련된 모든 기능을 담당한다.
node.js로 웹서버를 개발하는 것이 목표라면 잘 이해하고 넘어가야만 한다.
물론 후에 익스프레스 가지고도 만들어 볼 예정이나, 기본적으로 http모듈을 활용한 것 부터 잘 알아야 활용이 가능하다.
개념 | 설명 |
---|---|
요청 | 웹 서버에 보내는 모든 요청을 말합니다. |
http모듈 | http웹 서버와 관련된 모든 기능을 담은 모듈입니다. |
server객체 | 웹 서버를 생성할 때 사용하는 객체입니다. |
response객체 | 응답 메시지를 작성할 때 request이벤트 리스너의 두 번째 매개변수로 전달되는 객체입니다. |
request객체 | 응답 메시지를 작성할 때 request이벤트 리스너의 첫 번째 매개변수로 전달되는 객체입니다. |
CLIENT가 주소창에 https://www.naver.com을 입력했다면 naver주소로 해당 서버에 요청을 보낸 것이다. 그러면 naver는 응답으로 이미 정의된 메소드에 의해 메인 페이지를 렌더해준다. 요청과 응답은 이런 개념이다. 이처럼 요청과 응답에 의해 서버에서 해당 데이터를 전달하게 하는 일련의 과정이자 작업이라고 생각하면 좋겠다.
http모듈에서 가장 중요한 객체는 server 객체다. http모듈의 createServer() 메서드를 사용하면 server객체를 생성할 수 있다. 메서드를 하나씩 살펴보기 전에 코드를 통해 해당 부분을 뜯어보자.
// http모듈을 추출 -> 여기서 require= 기본적으로 리엑트에서 import와 비슷한 개념이다. 결국 http모듈을 받아와야 서버 구축을 할 수 있다고 생각해 두기.
const http = require('http')
// 웹 서버를 생성하는 메서드가 그냥 크리에이트다. 편하다~
const server = http.createServer()
//server 객체에 이벤트를 연결시키는 작업.
server.on('request', function (code) {
console.log('Request Event')
})
server.on('connection', function (code) {
console.log('Connection Event')
})
// 웹 서버를 3001 포트로 실행한다. 여기서 포트 번호는 변경될 수 있다.
server.listen(3001, function(){
console.log('3001번 포트로 서버가 실행되었습니다.')
})
2-2 실전 활용 예제
<script>
const http = require('http') // 1
const server = http.createServer((req, res) => { // 2
console.log('request received')
res.setHeader('Content-Type', 'application/json') // 3
res.end(JSON.stringify({ message: "Welcome to >wecode server! Http server without express" })) // 4
});
server.listen(3000, () => {
console.log('server is running on PORT 3000')
}) // 5
</script>
http
모듈을 가져와서 아래에서 사용할 수 있도록 변수에 담는 코드http.createServer
는 라는 메소드는 인자로 또 다른 함수를 받는다 (콜백함수). 인자로 받은 함수의 첫번째 인자는 http request 의 정보가 담겨있는 객체, 두번째 인자는 http response 객체다. 서버에 요청이 들어오면 이 인자로 받은 함수가 실행되는 꼴이다.application/json
형태로 세팅한다.res.end
함수를 통해서 요청에 대한 응답을 마무리 한다. 이 함수의 인자로 넘겨주는 값이 클라이언트가 받는 응답이 된다.server
는 앞서 생성한 서버를 의미하고 이 서버 객체의 listen
함수는 인자로 포트 번호와 콜백함수를 받습니다. 포트번호로 서버를 연다는 의미이며, 서버가 실행될 때의 로직을 콜백함수 안에서 처리할 수 있습니다. 보통 서버가 켜져있다는 로그 메시지를 남깁니다.이처럼 코드를 작성해서 터미널에서 돌려보면 아래와 같이 화면이 나오면 서버가 구축된 것이다.
이를 다시 웹에서 호출시켜 보면 다음과 같다.
물론, 기본적인 터미널에서도 실행은 가능하다.
뭐야? 이 정도면 그냥 서버 구축에 굳이 프레임 워크 필요없지 않나? 이런 생각이 들 수도 있다. 물론 필자 역시도 그렇게 생각했었다.
그렇다. 하나만 알고 둘은 몰랐었다. 그러면 왜 그럴까 한 번 살펴보자.
우리의 application(이하 앱)은 점점 더 규모가 커질 것이다. 유저를 회원가입도 시켜야하고, 로그인도 처리해야 하며, 프론트엔드 측에서 요구하는 상품에 대한 정보도 응답으로 보내줘야 한다.
이렇게 해당 자원에 대해 다른 함수(로직)을 실행하도록 하는 것을 라우팅 이라고 한다.
위의 상황에 대해서 의사코드를 작성하면 다음과 같이 된다.
<script>
const http = require('http')
const { sendPosts } = require('./sendPosts')
const server = http.createServer((req, res) => {
console.log
const { url, method } = req
res.setHeader('Content-Type', 'application/json')
if (url === '/') return res.end(JSON.stringify({ message: '/ endpoint' }))
if (url === '/signup' && method === 'POST') return res.end(JSON.stringify({ message: '회원가입 완료!' }))
if (url === '/login' && method === 'POST') return res.end(JSON.stringify({ message: '로그인 완료!' }))
if (url === '/products' && method === 'GET') return sendPosts(res)
res.end(JSON.stringify({ message: 'this response answers to every request' }))
})
server.listen(8000, () => { console.log('server is listening on PORT 8000')})
</script>
<script>
const sendPosts = (res) => {
res.end(
JSON.stringify({
products: [{
id: 1,
title: "node",
description: "node.js is awesome"
}, {
id: 2,
title: "express",
description: "express is a server-side framework for node.js"
}
]
}))
}
module.exports = { sendPosts } // withoutExpress.js 에서 사용하기 위해 모듈로 내보낸다.
</script>
보이는 바와 같이 라우팅을 직접 request 객체에서 url과 method 에 따라서 조건문으로 분기해서 다른 로직(SignUp, Login, sendPosts
)을 처리해 주어야 한다.
여기서 끝이 아니라 점점 더 앱의 규모가 커지게 된다면 어떡할까?
서버를 실행하는 함수 안에서 수많은 조건문과 수많은 로직을 모듈화 하는데 큰 공을 들이게 된다.
이런 불편함을 해소하기 위해서 탄생한 프레임워크가 Express다. 역시 개발자들은 귀차니즘을 정말 극도로 싫어하는 듯... ㅋㅋㅋ
일 전에 말씀드린 http에서 개선된 모듈 방식이다.
express 사이트(https://expressjs.com)에 가보면, express란 'Node.js를 위한 빠르고 개방적인 간결한 웹 프레임워크' 라고 한다.
그렇다. 잡설이 길었지만, express란, NodeJS를 사용하여 서버를 개발하고자 하는 개발자들을 위하여 서버를 쉽게 구성할 수 있게 만든 프레임워크다. 프레임워크란, 클래스와 라이브러리의 집합체라고 보면 된다.
더 짧게 말하면, express란 NodeJS를 사용하여 쉽게 서버를 구성할 수 있게 만든 클래스와 라이브러리의 집합체 라고 보면 된다.
이렇게 말만으로는 뭔 소리인지 모르니, 직접 코드로 봐야한다.
<script>
const http = require('http')
const express = require('express')
const { sendPosts } = require('./sendPosts')
const app = express()
app.get('/', (req, res) => {
res.json({ message: '/ endpoint' })
})
app.post('/signup', handleSignUp) // 첫번째 인자에는 endpoint url 을 기입하고,
app.post('/login', handleLogin) // 각각의 요청에 대해 핸들링 하는 함수를 두번째 인자로 넣습니다.
app.get('/products', sendPosts)
const server = http.createServer(app)
server.listen(8080, () => {
console.log('server is listening on PORT 3000')
})
</script>
위의 코드와 좀 다른 뭔가를 느끼는가? 그렇다. 먼저 조건문이 없어졌다. 그리고 app이 생겼다? 이게 뭐지?
🔥 **app.method('path', handler function)**
코드에서도 볼 수 있듯,
Express 모듈을 임포트 한 후 함수를 실행해서 app 이란 변수에 담는 것이 컨벤션 이다.
클라이언트의 요청을 처리하는 http server application 이라고 볼 수 있는 것.
이 앱을 사용하는 방법은 다음과 같다.
- 요청을 받을 http method로 함수를 app 에 붙인다.
- 요청을 받을 endpoint url 을 string 으로 첫번째 인자에 넣는다.
- 두번째 인자는 요청과 응답을 인자로 받는 콜백함수이다.
즉, 각각의 메소드와 엔드포인트에 대해 처리하는(핸들링) 함수가 된다.
<script>
const sendPosts = (req, res) => {
res.json({ // 위에서 작성한 sendPosts 함수와 비교했을 때,
// express 덕분에 JSON.stringify 함수를 사용할 필요없이
// response 객체의 json 메소드를 활용합니다.
products: [
{
id: 1,
title: 'node',
description: 'node.js is awesome',
},
{
id: 2,
title: 'express',
description: 'express is a server-side framework for node.js',
},
],
})
}
module.exports = { sendPosts } // routing.js 에서 사용하기 위해 모듈로 내보낸다.
</script>
익스프레스에 맞게 센드포스트 역시 형태를 바꿔줘야 한다. 안하면 에러가 난다.
자~ 요약해 보자면, 익스프레스를 쓰는 이유는...
- Express 없이 서버를 구현한 코드와 비교해 봤을 때, 아직은 크게 와닿지 않을 수도 있지만
- 조건문으로 라우팅을 처리했던 것이 간편해졌고
- 각각의 요청을 처리하는 함수의 분리로 인해
- 직관적으로 코드를 설계할 수 있다는 장점을 가지고 있다.
다음 편에서는 리스폰스 함수들의 차이에 대해 한 번 비교를 해보겠다.
-------------------to be continued-------------------