Express 데이터베이스 연결하기

서나무·2022년 10월 11일
0
post-thumbnail

🐘 Postgresql 연결하기

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에 데이터 저장하기

이제 로컬로 개발되어 있던 부분을 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' });
});

📌 글 CRUD

먼저 글 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: '글을 삭제했습니다.' });
});

이제 기능 개발은 끝이다!

profile
주니어 프론트엔드 개발자

0개의 댓글