
우리가 만든 코드를(우리가 아니라 내가 혼자 만들긴 함ㅋ) 다음 순서로 살펴볼 것이다.
저번에 프론트엔드를 봤으니 이번에는 백엔드를 볼 거다.
💡 server.js
Express와 SQLite를 사용해서 간단한 서버를 만들었다.
const express = require("express");
const bodyParser = require("body-parser");
const db = require("./db/db"); // SQLite 설정 모듈
const app = express();
const PORT = 3000;
app.use(bodyParser.json());
app.use(express.static("../frontend"));
// To-Do 목록 가져오기
app.get("/api/todos", (req, res) => {
db.all("SELECT * FROM todos", [], (err, rows) => {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ todos: rows });
});
});
// 새로운 To-Do 추가
app.post("/api/todos", (req, res) => {
const { task } = req.body;
db.run("INSERT INTO todos (task) VALUES (?)", [task], function (err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ id: this.lastID });
});
});
// To-Do 삭제
app.delete("/api/todos/:id", (req, res) => {
const { id } = req.params;
db.run("DELETE FROM todos WHERE id = ?", id, function (err) {
if (err) {
res.status(500).json({ error: err.message });
return;
}
res.json({ deletedID: id });
});
});
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
분해해서 보면,
Express 모듈을 불러와서 express 변수에 저장한다.
Express는 Node.js위에서 웹 서버를 쉽게 만들 수 있게 도와주는 도구(프레임워크)다. 웹 서버를 만들 때 코드를 일일이 짜는 게 아니라 Express가 제공하는 기본 틀을 사용하면 편하다.
Body-Parser 모듈을 불러와서 bodyParser 변수에 저장한다.
Body-Parser는 클라이언트가 서버로 보낸 요청 데이터를 JSON 형식으로 파싱(해석)하는 미들웨어(요청이 서버로 전달되기 전에 중간에서 요청을 처리하거나 변환하는 역할)다. 전술했듯, 서버와 클라이언트는 HTTP 객체로 소통하기 때문에 그 자체가 JSON은 아니다. JSON으로 변환해야 한다.
db/db.js 파일을 불러와서 db 변수에 저장한다.
db.js는 SQLite의 데이터베이스와 연결해서 데이터를 저장하거나 조회할 수 있는 설정이 들어있다.
다음으로 넘어가기 전에,,
- require는 Node.js에서 모듈(독립적으로 작성된 코드 묶음, 유지보수를 위해 기능별로 코드를 모듈화함)을 가져올 때 사용하는 함수다.
- 라우트(Route)는 특정 URL 경로와 그 경로에 대한 요청을 처리하는 방법을 정리한 것이다. 이따가 볼 것이다.
app이 바로 우리가 만든 웹 서버의 본체다.
3000번 포트는 개발용 웹 서버를 만들 때 관례적으로 사용되는 숫자다. 다른 걸 써도 된다.
포트는 0번부터 65535번까지 총 65536개가 있다. 포트는 컴퓨터가 네트워크와 통신할 때 어떤 프로그램이 어떤 데이터를 받을지 구분하는 번호다. 하나의 포트는 동시에 하나의 프로그램만 사용할 수 있다.
첫 번째 줄은 bodyParser.json() 미들웨어를 app에 추가하는 역할을 한다.
얘는 클라이언트의 요청을 JSON으로 바꿔준다.
두 번째 줄은 app이 프론트엔드의 파일을 클라이언트에게 제공하기 위한 설정이다.
localhost:3000에 접속했을 때 자동으로 frontend 폴더 안에 있는 index.html을 찾아서 보여준다. (static 설정을 안 하면 index.html이 기본으로 보이지 않는다.)
다음으로 API 엔드포인트를 정의하자.
먼저 GET을 정의하자.
클라이언트가 `/api/todos` 경로로 GET 요청을 보내면 아래 내용을 실행한다. (경로는 아무렇게나 정할 수 있다. 다만 관례상 이렇게 하는 것이다. 특히 접두사로 /api를 사용하는 것이 좋다.)
오류가 나면 오류를 알려주고(500번 서버 에러) 아니면 정상적으로 결과를 반환하면 된다.
다음으로는 POST 요청을 보자.
req.body에서 추출한 값을 task 변수에 넣는다.
클라이언트가 열심히 보낸 파일이 담겨 있다.
다음은 SQL 쿼리다.
db는 우리가 앞에서 정의한 db.js인데 여기랑 같은 선상에 todos.db가 있으니 이걸 자동으로 불러온다.
두 번째 파라미터의 [task]는 위에서 정의한 변수 task와 동일한데 SQL 쿼리 안에 있는 (task)는 이거랑 굳이 이름이 같을 필요는 없고 todos.db에서 해당하는 칼럼의 이름을 넣기만 하면 된다. 이 [task]는 앞에 있는 (?)안에 삽입되는데 이렇게 한 이유는 SQL Injection 공격을 방지하기 위함이다.
에러가 나면 처리한다.
성공하면 반환한다. id: this.lastID를 반환하는 것은 곧 방금 추가된 항목의 ID를 반환하는 것을 뜻한다. POST니까 사실 아무것도 반환 안 해도 된다. 데이터베이스만 고치면 되기 때문이다. 하지만 실무에서는 클라이언트가 유용하게 활용할 수 있는 추가 정보를 포함하는 경우가 많다.
201번 Created satatus code를 넣는 것이 권장되지만 여기서는 생략했다.
다음은 DELETE다.
req.params에는 요청 경로의 파라미터 정보를 담고 있다.
여기도 SQL 쿼리 안에 있는 `id`는 todos 테이블의 칼럼명이다. req.params로 뽑은 id와는 다른 것이다.
이제 PORT를 통해 서버가 실행된다.
💡 db.js
이건 SQLite 데이터베이스를 설정하고 todos 테이블을 생성한다.
const sqlite3 = require("sqlite3").verbose();
const db = new sqlite3.Database("./db/todos.db");
// 테이블이 없을 경우 생성
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task TEXT
)
`);
});
module.exports = db;
`sqlite3` 라이브러리는 SQLite 데이터베이스와 상호작용할 수 있도록 도와준다. verbose()를 하면 디버깅을 위한 추가 정보를 볼 수 있다.
두 번째 줄을 통해 해당 경로에 있는 데이터베이스 파일을 연다. 그 객체를 db 변수에 저장한다.
serialize()는 데이터베이스 작업을 순차적으로 실행하게 한다.
db.run()을 통해 SQL 쿼리를 실행한다. 테이블을 만든다.
module.exports = db;를 통해 db를 외부에서 사용할 수 있도록 내보낸다.
이렇게 하면 다른 파일에서 `require`를 사용해서 이 데이터베이스에 연결할 수 있다. 내보내는 변수의 이름이 db인지 db2인지는 상관없다. server.js에서 가져오기 위해서는 그저 db.js를 언급하면 되기 때문이다.
자네들은 프론트엔드, 백엔드, 데이터베이스가 포함된 초간단 서비스를 완성했다!
'분명 전산학부 졸업 했는데 코딩 개못하는 조준호 > Web' 카테고리의 다른 글
한국은행 들어갈 때까지만 합니다
조만간 티비에서 봅시다