들어가기에 앞서
안녕하세요 ! SIAN 입니다 😆 JAVA & SPRING 프로그래밍에 익숙한 저에게 JavaScript & Express 는 어렵게만 느껴졌습니다. 학습하며 떠올랐던 의문점과 그에 대한 답변을 적는 방식으로 서술하여 처음 JavaScript & EXPRESS 를 접하시는 분들도 부담없이 읽으실 수 있도록 작성하기 위해 노력했습니다. 가장 쉽게 설명하는 Express 튜토리얼을 시작합니다 !
Express 튜토리얼
Express 를 설명하기 전에 Node 에 대해 간단히 알아보겠습니다.
Express 는 Node 를 위한 웹 프레임워크이기 때문입니다.
Node 란 무엇인가
Mozila 공식문서에서 Node는 개발자가 모든 종류의 서버사이드 도구들과 어플리케이션을 JavaScript 로 만들 수 있도록 해주는 런타임 환경이이라고 정의되어있습니다.
언어도 아니고, 프레임워크도 아니고, 라이브러리도 아니고, 런타임 ? 그렇다면 ..
런타임 환경은 무엇인가
런타임은 프로그래밍 언어가 구동되는 환경으로, 어떤 프로그램이 동작하는 장소입니다.
프로그램이 동작하기 위해선 빌드하기 위한 도구들이 필요하고, 따라서 런타임에는 JavaScript 엔진과 Web API 가 필수적입니다.(JavaScript 엔진은 JavaScript 를 해석할 수 있는 엔진입니다) JavaScript 의 런타임은 대표적으로 웹 브라우저가 있습니다.
새로운 런타임 Node 는 왜 생겨난걸까
JS는 이미 이미 웹 브라우저 라는 런타임을 갖고 있는데 왜 Node 라는 새로운 런타임이 생겨난걸까 ? 라는 의문이 듭니다.
최초의 JavaScript 는 웹페이지(HTML)를 조작하기 위해서 만들어졌습니다. 이후 2005년 Google Maps 의 등장으로 웹 애플리케이션 언어로서 JavaScript 의 기능이 확인되었고, JS를 통해 웹 애플리케이션을 구축하려는 시도와 활용이 많아지면서 JavaScript 엔진에 대한 요구가 상승했습니다.
많은 브라우저가 자바스크립트 엔진 개발 경쟁을 벌이는 과정에서 V8 엔진의 성능이 향상했습니다. JS 엔진이 강력해지자 웹 브라우저 내에서 말고도 JS를 실행할 수 있는 환경이 요구되었고, 브라우저 없이 자바스크립트 문법을 실행할 수 있는 Node.js 런타임이 생겨났습니다. 이렇듯 브라우저와 Node.js 는 JavaScript 의 런타임 환경이라는 동일 조건을 가지고 있으나,탄생 배경이 다르기 때문에 둘의 존재 목적이 다릅니다.
- 브라우저 : HTML, CSS, JS 를 실행하여 웹페이지를 화면에 띄우는 것이 목적
- Node.js : JS 를 브라우저 외의 환경에서도 사용하기 위한 것이 목적
Express 란 무엇인가
Node 소개는 Express를 소개하기 위한 빌드업일 뿐이었습니다.
Express 는 Node.js 웹서버에 올라가는 프레임워크이며, Express 를 통해 Node.js 서버를 쉽게 구성할 수 있습니다.
왜 ? 어떻게 ? Express 를 통해 Node.js 서버를 쉽게 구성할 수 있는것인가 ?
Express 로 쉽게 서버를 구성할 수 있는 요인으로는 미들웨어, 라우팅 등이 있습니다.
- Middleware
미들웨어란 요청과 응답 중간에서 목적에 맞게 처리를 하는 함수입니다.
* 미들웨어 함수는 요청(req) 객체, 응답(res) 객체 그리고 다음 미들웨어를 실행하는 next 함수로 이뤄져있습니다.
* 미들웨어 함수는 요청과 응답의 과정 도중 그 다음의 미들웨어 함수를 실행시킬 수 있습니다.
* 미들웨어는 HTTP 요청 순차적으로 시작되기 때문에 순서가 중요합니다.
* 미들웨어 함수가 요청-응답을 사이클을 종료하지 않는 경우 next() 를 호출해 그 다음 미들웨어 함수에 제어를 전달해야 한다. 그렇지 않으면 해당 요청은 정지된 채로 방치됩니다.
간단한 미들웨어를 만들고 작동시켜보겠습니다. 다음은 실행 시 콘솔에 로그를 찍는 미들웨어입니다.
app.use((req,res) => {
console.log("middleware test");
});
이렇게 미들웨어를 작성 후 서버 실행해봤더니 로그만 찍히고 페이지 접속이 되지 않습니다. 이것은 next() 함수로 다음 미들웨어 함수를 실행시키지 않았기 때문입니다. 다음 메서드로 넘어가는 것에 실패했습니다. 요청과 응답의 중간에서 실행된다는 미들웨어의 특징으로 인해 아무것도 반환하지 못하고 무한정 서버의 응답을 기다리게 된 것입니다. next() 를 추가 후 다시 실행을 시켜보겠습니다.
app.use((req,res,next) => {
req.user ={
name : "SIAN",
};
next();
});
app.get('/', function (req, res) {
console.log(req.user);
res.sendFile(__dirname+"/sian-middleware.html")
});
의도한대로 콘솔에 로그가 찍히고 페이지에 접속되었습니다.
- 라우팅
라우팅은 네트워크 안에서 통신되는 데이터를 보낼 경로를 결정하는 프로세스입니다.
- 프로젝트의 규모가 커질수록 라우터를 분리시키는 과정이 필요합니다. Express의 Router() 을 사용하면 여러개의 JS 파일에서 라우팅이 가능해집니다. 라우터를 분리시키고 Route()함수를 이용하여 라우팅을 해보겠습니다. 루트에 router 폴더 생성 후 그 아래에 라우팅을 할 JS 파일을 생성해주세요.
test.js
const express = require('express');
const router = express.Router();
router.get('/route', (req, res) => {
res.send("라우팅")
});
module.exports = router;
생성한 JS파일에 라우터 미들웨어를 작성했습니다. 작성된 라우터 미들웨어는 사용자로부터 요청받은 req 객체를 처리한 후 res 를 리턴합니다.
app.js
const express = require('express');
const testRoute = require('./router/test');
app.use(testRoute);
app.listen(4000, ()=> {
console.log('Listening on port 4000');
});
생성한 라우터 파일을 use() 안에 넣어 미들웨워화했습니다. 이제 (‘/route’) 경로로 들어오는 요청은 라우터를 거치게되었습니다. 서버를 실행시키면 의도한대로 라우팅되는 것을 확인할 수 있습니다.
Express REST API
간단하게 Express 가 무엇인지, 왜 사용하는지에 대해서 알아봤습니다. Express 프레임워크는 어떤 과정으로 실행되는지 감이 오시나요 ? 모두 이해되지 않으셔도 괜찮습니다. 함께 REST API 를 작성해보며 모두 이해하실테니까요. 이제 겨우 한발짝 떼었는데 API 를 작성한다니 걱정이 되실수도 있겠습니다.
1+1 을 배우고 미적분으로 응용하는 식의 학습자료가 참 많아 원망스러운 적이 많았었는데요, 이제 1+1 를 배워봤으니 2+2 로 응용하는 난이도로 진행해보겠습니다.
Express 가 무엇인지, 그리고 왜 사용하는지에 대해 정리해봤습니다. DB를 붙여 (MySQL) 아주 간단한 REST API 를 만들어보겠습니다.
프로젝트의 구조는 다음과 같습니다
메인 애플리케이션인 index.js, 쿼리를 작성해 DB 에서 데이터를 가져오는 db.js, 그리고 routes 폴더 내 jjigae.js 에서 데이터를 가공하겠습니다.
index.js
//1
const express = require("express"); //express 모듈
const bodyParser = require('body-parser');
const PORT = 3001;
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
//2
const jjigaeRouter = require('./routes/jjigae');
app.use('/jjigae',jjigaeRouter);
//3
app.listen(PORT, () => console.log(`Running on port ${PORT}`));
1
express 에서 body 에서 데이터를 꺼내 쓰려면 body-parser 가 필요합니다. 클라이언트에서 HTTP Request 를 통해 요청한 것을 jjigaeRouter 에서 꺼내 쓸 예정이기 때문에 미들웨어에 bodyParser 를 태워(?) 줍니다. express 4.16.0 버전부터 body-parser 가 express 에 내장되어 body-parser 모듈을 불러오는 것을 생략하고 아래와 같이 작성할 수 있습니다.
app.use(express.json());
app.use(express.urlencoded({ extended: true}));
2
라우팅을 Spring 친화적(?) 으로 다시 설명해보자면 MainController 의 역할을 하는 것이 메인애플리케이션 (index.js) 입니다. index.js 는 어떤 방식으로 처리를 할지 정의하는데, 이때 들어온 요청을 특정 메서드와 매핑하기 위해 라우터를 사용하는 것입니다 (@RequestMapping 어노테이션이 생각나지 않나요 ?)
‘/jjigae’ 로 들어온 요청들은 jjigaeRouter 로 보내겠다고 미들웨어로 명시했습니다.
3
마지막으로 앱이 실행되었을 때 어느 포트에서 작동하는지 확인할 수 있는 로그를 띄웠습니다.
조회하기
전체 리스트 조회
db.js
const mysql = require("mysql");
const connection = mysql.createConnection({
host: "{localhost}",
user: "root",
password: "{password}",
database: "Jjigae",
port: "3306",
});
DB 와 서버를 연결하는 객체 createConnection() 에 DB 에 관한 정보를 넣습니다.
function getAllJjigae(callback){
connection.query(`SELECT * FROM JJIGAE`,(err, rows)=>{
if(err) throw err;
callback(rows);
});
}
JJIGAE 테이블의 모든 데이터를 가져오는 메서드입니다. query() 의 기본형태는 다음과 같습니다.
connection.query(sql, function(error, rows, fields){
}
첫번째 인자로 SQL 구문을 받습니다. 콜백의 rows 는 구분에 해당하는 행을, fields 는 열을 배열 형태로 받습니다. 이렇게 DB 받아온 데이터를 callback() 의 인자로 넣어 전달합니다.
(콜백함수 : 다른 함수의 인자로써 넘겨진 후 특정 이벤트에 의해 호출되는 함수. 이 때 함수는 포인터나 람다식 등으로 전달됩니다)
jjigae.js 전달받은 전체 찌개 리스트 데이터를 응답합니다.
router.get('/',(req,res,next)=>{
db.getAllJjigae((rows)=>{
res.send(rows);
});
});
조회하기
조건에 알맞은 데이터 조회
db.js
function getJjigaeByKey(key, value, callback){
connection.query(`SELECT * FROM JJIGAE WHERE ${key}=${value}`,(err, row)=>{
if(err) throw err;
callback(row);
});
}
조건을 id 로 설정한 쿼리문 , name 으로 설정한 쿼리문 2개를 작성해야했는데 쿼리문에 변수를 넣어 메서드 하나로 통합했습니다. 두개 메서드를 만드는 것이 훨씬 깔끔했을것이라고 생각됩니다.
jjigae.js
router.get('/:id',(req,res)=>{
const key = 'id';
db.getJjigaeByKey(key, req.params.id, (row)=>{
res.send(row);
});
});
인자로 key 와 value 를 넣어줬습니다. Express 에서 데이터를 서버에 보내는 방법 중 Query Params 방식을 사용했습니다. 작동방식은 Spring 의 @PathVariable 어노테이션과 동일합니다.
@GetMapping("/{id}")
public get_method(@PathVariable("id") int id){
..
};
데이터는 params 에 담겨오기때문에 req.params.{path} 형태로 값을 가져올 수 있습니다.
추가하기
새로운 객체 등록
db.js
function insertJjigae(object, callback){
connection.query(`INSERT INTO JJIGAE(name, description) VALUES ('${object.name}', '${object.description}')`,(err, result)=>{
if(err) throw err;
callback(result);
});
}
jjigae.js
router.post("/", (req, res) => {
db.insertJjigae(req.body, () => {
res.redirect("/jjigae");
});
});
삭제하기
조건에 알맞은 데이터 삭제
db.js
function deleteJjigae(id, callback){
connection.query(`DELETE FROM JJIGAE WHERE ID=${id}`, (err, result)=>{
if(err) throw err;
callback();
});
}
jjigae.js
router.delete("/:id", (req, res) => {
const id = req.params.id; //Query Params
db.deleteJjigae(id, () => {
res.redirect("/jjigae");
});
});
수정하기
조건에 알맞은 데이터 수정
db.js
function updateJjigaeByName(object, callback){
connection.query(`update jjigae set description = '${object.description}' where name = '${object.name}'`,(err)=>{
if(err) throw err;
callback();
});
}
jjigae.js
router.put("/", (req, res) => {
const key = "name";
const value = "'" + req.body.name + "'";
let detailResponse;
db.getJjigaeByKey(key, value, (row) => {
detailResponse = row;
});
if(!detailResponse){//새로운 객체는 수정이 불가하므로 문자열로 응답
res.send('새로운 객체는 post 요청 ! ');
}
db.updateJjigaeByName(req.body, () => {
res.redirect("/jjigae");
});
});
글을 마치며
이렇게 REST API 를 만들어 Express 응용을 해보았습니다. 2+2 응용이 맞지요 ? Express 을 알고자 하는 분들께 조금이나마 도움이 되었길 바랍니다. 다음엔 어떤 주제를 가져와서 함께 공부해볼지 떠올리니 신이 납니다. 읽어주셔서 감사합니다.