Express에서 postgresql 데이터베이스와 연결하기 위해서는 pg 라이브러리를 사용해야 한다.
$ npm i pg
db 폴더에 index.js 파일을 생성해서 Database class를 선언하고 Database 인스턴스를 생성해서 외부에서는 생성된 인스턴스를 사용했다.
new 키워드로 인스턴스가 생성되면, 생성자에서 connect 함수를 호출해서 데이터베이스와 연결하도록 했다.
db/index.js
const { Pool } = require('pg');
class Database {
#database;
constructor() {
this.#connect();
}
async #connect() {
this.#database = new Pool({
user: 'tester', // Database 소유자
host: 'localhost', // Host
database: 'test_db', // Database 이름
password: 'qwer', // Database 비밀번호
port: 5432, // Database 포트
});
try {
await this.#database.connect();
} catch (error) {
console.log('Database connect failed');
}
}
async query(_query) {
return await this.#database.query(_query);
}
}
const database = new Database();
module.exports = database;
database 객체를 private으로 선언했기 때문에, query 함수를 선언해서 사용했다.
이제 로컬로 개발되어 있던 부분을 database에 저장하도록 수정해야 한다.
로그인, 회원가입을 할 때 id로 사용자를 찾는 로직이 반복되어서 하나의 함수로 만들어서 가독성을 높였다.
user/index.js
const databaes = require('../db');
async function getById(_id) {
try {
const { rows } = await database.query(`select * from users where id='${_id}'`);
return rows[0];
} catch (error) {
return null;
}
}
query 함수는 rows에 쿼리 결과를 담아서 준다. query 함수의 반환 값이 객체이기 때문에, 구조분해할당으로 rows를 받아와서 0번째 요소를 반환해주도록 코드를 작성했다.
그리고 사용자를 생성하는 함수도 따로 선언했다.
async function create({ id, password, name}) {
try {
await database.query(`
insert into users (id, password, name)
values ('${id}', '${password}', '${name}')
`);
return true;
} catch (error) {
return null;
}
}
이제 getById, create 함수를 사용해서 회원가입, 로그인 기능을 수행하는 코드를 수정해보자.
// 회원가입
app.post('/user/register', async (req, res) => {
const { id, password, name } = req.body;
// id, password, name이 있는지 체크한다.
if (!id || !password || !name) {
res.status(400).send({ message: 'id, password, name은 필수입력 사항입니다.' });
return;
}
// id는 중복되지 않도록한다.
const user = await getById(id);
if (user) {
res.status(400).send({ message: '이미 존재하는 아이디입니다.' });
return;
}
// 사용자를 추가한다.
const result = await create(req.body);
if (result) {
res.send({ message: '사용자를 등록했습니다.' });
} else {
res.send({ message: '사용자 등록에 실패했습니다. '});
}
});
// 로그인
app.post('/user/login', (req, res) => {
const { id, password } = req.body;
// id, password가 있는지 체크한다.
if (!id || !password) {
res.status(400).send({ message: 'id, password는 필수입력 사항입니다.' });
return;
}
// 입력받은 id의 사용자를 찾는다.
const user = await getById(id);
if (!user) {
res.status(400).send({ message: '존재하지 않는 사용자입니다.' });
return;
}
// 입력받은 password와 찾은 사용자의 password가 일치하는지 체크한다.
if (user.password !== password) {
res.status(400).send({ message: '비밀번호가 일치하지 않습니다.' });
return;
}
// 토큰을 발급한다.
res.status(200).send({ token: 'token' });
});
먼저 글 id로 조회하기, 생성, 수정, 삭제 기능을 수행하는 함수를 만들었다.
회원가입 기능과 마찬가지로, 쿼리를 잘 수행했으면 true를 반환하고 오류가 발생하면 null을 반환하도록 했다.
그리고 글 마다 고유한 id를 부여하기 위해 uuid 라이브러리를 사용했다.
$ npm i uuid
post/index.js
// id로 글 조회
async function getById(_id) {
try {
const { rows } = await database.query(`
select _posts.id, name, title, content, created_on
from _posts
inner join _users
on _posts.user_id = _users.id
where _posts.id = '${_id}'
`);
return rows[0];
} catch (error) {
return null;
}
}
// 글 생성
async function create({ user_id, title, content }) {
try {
await database.query(`
insert into _posts (id, user_id, title, content, created_on)
values ('${uuid.v1()}', '${user_id}', '${title}', '${content}',
'${new Date().toISOString()}')`);
return true;
} catch (error) {
return null;
}
}
// 글 수정
async function update({ user_id, id, title, content }) {
const post = await getById(id);
if (!post) {
return null;
}
try {
await database.query(`
update _posts
set title='${title}', content='${content}'
where id='${id}'`);
return true;
} catch (error) {
return null;
}
}
// 글 삭제
async function remove({ user_id, _id }) {
const post = await getById(_id);
if (!post) {
return null;
}
try {
await database.query(`delete from _posts where id='${_id}'`);
return true;
} catch (error) {
return null;
}
}
쿼리를 작성하면서 콤마나 따옴표 등 오타를 많이 내서 많은 오류가 났었다..
글 생성, 수정, 삭제 기능의 경우 user_id가 필요하다. 그래서 하지만 매번 token이 유효한지 검사하는건 비효율적이기 때문에, 미들웨어를 사용해서 user_id를 request 객체에 저장하려고 한다.
middleware/tokenVerify.js
const tokenService = require('../jwt');
const tokenVerify = (req, res, next) => {
// Request headers에 토큰이 없으면 오류를 반환한다.
if (!req.headers.authorization) {
res.status(400).send({ message: '토큰이 존재하지 않습니다.' });
return;
}
try {
const payload = tokenService.getPayload(req.headers.authorization);
// request 객체에 사용자 아이디를 저장한다.
req.user_id = payload.user_id;
} catch (error) {
// 토큰 payload를 얻는데 실패하면 유효한 토큰이므로 client에 알려준다.
res.status(400).send({ message: '유효하지 않은 토큰입니다.' });
return;
}
next();
};
module.exports = tokenVerify;
미들웨어는 라우터에서 미들웨어를 실행하고 싶은 위치에 넣어주면 된다.
나는 글 생성, 수정, 삭제 기능을 수행하기 전에 토큰이 유효한지 체크하는 미들웨어를 실행시키도록 했다.
post/index.js
router.post('/', tokenVerify, async (req, res) => { //... }
router.put('/', tokenVerify, async (req, res) => { //... }
router.delete('/', tokenVerify, async (req, res) => { //... }
이제 글 CRUD 기능을 마무리 해보자.
// 글 전체 조회
router.get('/', async (req, res) => {
try {
const { rows } = await database.query(`
select _posts.id, name, title, created_on
from _posts inner join _users on _posts.user_id = _users.id
`);
res.send(rows);
} catch (error) {
res.status(400).send({ message: '글 조회중 오류가 발생했습니다.' });
}
});
// 글 단일 조회
router.get('/:id', async (req, res) => {
const post = await getById(req.params.id);
// 글이 존재하는지 체크
if (!post) {
res.status(400).send({ message: '존재하지 않는 글입니다.' });
return;
}
res.send(post);
});
// 글 등록
router.post('/', async (req, res) => {
const { title, content } = req.body;
// title, content가 있는지 체크
if (!title || !content) {
res.status(400).send({ message: 'Title, content는 필수 입력 사항입니다.' });
return;
}
// 글 추가
const result = await create({ title, content });
if (!result) {
res.status(400).send({ message: '글 등록에 실패했습니다.' });
return;
}
res.send({ message: '글을 등록했습니다.' });
});
// 글 수정
router.put('/:id', async (req, res) => {
const { title, content } = req.body;
// id, title, content가 있는지 체크
if (!req.params.id || !title || !content) {
res.status(400).send({ message: 'id, title, content는 필수 입력 사항입니다.' });
return;
}
// 글 수정
const result = await update({ id: req.params.id, user_id: req.user_id, title, content });
if (!result) {
res.status(400).send({ message: '글 수정에 실패했습니다.' });
return;
}
res.send({ message: '글을 수정했습니다.' });
});
// 글 삭제
router.delete('/:id', async (req, res) => {
// id 있는지 체크
if (!req.params.id) {
res.status(400).send({ message: 'id는 필수 입력 사항입니다.' });
return;
}
// 글 삭제
const result = await remove({ id: req.params.id, user_id, req.user_id });
if (!result) {
res.status(400).send({ message: '글 삭제에 실패했습니다.' });
return;
}
res.send({ message: '글을 삭제했습니다.' });
});
이제 기능 개발은 끝이다!