DB에 SQL을 이용해 Query 를 요청한다 라는 의미로 Raw Query 라고 부른다.
우리는 SQL을 Node.Js 에서 사용하고 있는데, 이를 통해 DB에 Query 를 요청하는 작업을 진행할 것이고 이를 학습하는 과정을 진행한다.
# yarn으로 프로젝트를 초기화합니다.
yarn init -y
# express와 mysql 드라이버를 설치합니다.
yarn add express mysql2
mysql12 드라이버는 MySQL DB를 Node.Js 사용하게 해주는 라이브러리이다.
DB와 개발 언어를 연결하는 역할을 맡으며, 이를 '데이터베이스 드라이버' 라고도 부른다.
// app.js
import express from 'express';
import mysql from 'mysql2';
const connect = mysql.createConnection({
host: 'express-database.clx5rpjtu59t.ap-northeast-2.rds.amazonaws.com', // AWS RDS 엔드포인트
user: 'root', // AWS RDS 계정 명
password: 'aaaa4321', // AWS RDS 비밀번호
database: 'Express_DB', // 연결할 MySQL DB 이름
})
const app = express();
const PORT = 3017;
app.use(express.json());
app.listen(PORT, () => {
console.log(PORT, '포트로 서버가 열렸어요!');
});
connect 변수 선언의 4가지 속성(host, user, password, database) 를 통해 선택한 DB와 연결하는 과정을 거친다.
저기서 database 는 연결할 DB의 이름을 말한다.
DB의 이름은 전에 연결했던 vscode 의 좌측 하단
여기서 확인할 수 있는데, 우리는 이 중에서 Express_DB 에 연결하는 과정을 진행한다.
DB의 연결 속성을 간단히 설명하고 넘어간다
1. host : mysql12 데이터베이스 드라이버가 접속할 DB의 주소
2. user : AWS RDS DB의 계정 명
3. password : AWS RDS DB의 비밀번호
4. database : AWS RDS DB의 이름
그럼 이제 이렇게 연결된 DB를 가지고 API를 구현하는 실습을 진행한다.
먼저 테이블 구조를 살펴보자.
다음과 같은 양식으로 만들 것이다.
// app.js
/** 테이블 생성 API **/
app.post('/api/tables/', async (req, res, next) => {
const { tableName } = req.body;
await connect.promise().query(`
CREATE TABLE ${tableName}
(
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
createdAt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)`);
return res.status(201).json({ message: '테이블 생성에 성공하였습니다.' });
});
코드를 보면 알겠지만 SQL 문법을 그대로 적용하여 JavaScript 안에 작성한 것이다.
` ` 백틱을 사용하여 이 안에 작성하여 원문 그대로를 적용시킬 수 있도록 하는 방법이다.
테스트용 클라이언트 Insomnia 로 테스트를 해보면
response 가 정상적으로 반환되는 것을 확인할 수 있다.
마찬가지로 테이블 또한 정상적으로 만들어졌다.
사실 이 테이블은 보통 prisma 를 사용하여 prisma.schema 를 통해 테이블 구현 방법을 주로 채택한다.
테이블의 목적 상 클라이언트가 직접 만드는 경우는 없거나 극소수일 뿐더러 관리 권한은 개발자가 쥐고 있어야 하기에, 이런 방법으로도 테이블 생성이 가능하다~ 정도만 알아두어도 좋을 듯 하다.
SQL 명령어로는 SHOW TABLES 가 있다.
이 API의 경우 return 으로 response 를 전달할 것인데, 해서 변수를 하나 만들고 이에 할당해 반환하는 방법으로 로직을 작성한다.
// 테이블 목록 조회 api
app.get('/api/tables', async (req, res, next) => {
const qeuryResult = await connect.promise().query(`
show tables
`)
return res.status(200).json({ message: qeuryResult });
});
마찬가지로 테스트를 돌려보면
다음과 같이 나타난다.
첫번째 인덱스에 해당하는 데이터가 테이블의 목록을 말한다. 사진이 작아 잘 안보이긴 하는데, 방금 생성했던 'RawQueryDB'가 반환되었다.
두번째 배열은 buffer 타입이다.
첫번째 테이블 생성 API에서 만들었던 데이터들이 전부 숫자타입으로 출력되는 것인데 이는 사용하지 않고 RawQueryDB에 대한 정보만 활용할 것이다.
해서 return 결과인 queryResult 데이터를 가공하는 과정을 진행해보자.
// 테이블 목록 조회 api
app.get('/api/tables', async (req, res, next) => {
const [tableList] = await connect.promise().query(`
show tables
`)
const tableName = tableList.map(table => Object.values(table)[0]);
return res.status(200).json({ tableList: tableName });
});
첫번째 show tables 시행으로 나온 결과들을 tableList 객체에 먼저 할당한다.
이후 매핑을 통해 그 객체 안의 0번째 인덱스의 값을 tableName 변수에 저장하고 이를 response 를 통해 반환하는 과정이다.
해서 다음과 같이 테이블의 이름만 나타난다면 정상적으로 데이터를 가공한 것이다.
말 그대로 테이블에 데이터를 추가하는 API이다.
SQL 문법으로는 INSERT INTO 를 사용하며, 로직 상으로는 클라이언트로 URL 의 params 로 전달받은 테이블의 이름에 body 로 전달받은 데이터를 추가하도록 구현할 것이다.
// 데이터 삽입 api
app.post('/api/tables/:tableName/items', async (req, res, next) => {
// 테이블 이름, name 컬럼에 할당될 값
const {tableName} = req.params;
const {name} = req.body;
await connect.promise().query(`
insert into ${tableName} (name)
values ('${name}')
`);
return res.status(201).json({message : "데이터 생성 성공"});
});
앞서 말했듯 여기서 전달받은 데이터는 총 2가지, params 와 body 데이터이다.
params 는 상단의 /api/tables/:tableName/items 의 tableName 으로 전달되어 req.params 를 통해 코드에서 사용한다.
이를 tableName 으로 할당해주었다.
또한 body 로 전달받은 데이터가 있는데, 이 부분이 직접적으로 사용자가 전달하는 데이터가 되며 코드 상으로는 req.body를 통해 전달 및 name 변수에 저장해주었다.
테스트에서는 body로 '데이터 삽입 테스트1' 이라는 name 값과 params 로 RawQueryDB 를 전달해주었다.
참고로 테스트 클라이언트에서 URL 을 작성할 때, 코드에서 작성한 것과 동일하게 .../tables/: ... 에서 : 를 붙여주면 안된다. 이는 이 다음 문자열이 params 라는 것을 나타내기 위한 표시로, 테스트 URL 에서 작성할 경우 에러가 발생한다.
SQL의 SELECT 문법을 이용하는데, 일단은 간단하게 params 로 전달받은 tableName 에 해당하는 모든 데이터를 조회하도록 구현해보자.
// 데이터 조회 api
app.get('/api/tables/:tableName/items', async (req, res, next) => {
const { tableName } = req.params;
const [itemList] = await connect.promise().query(`
select id, name, createdAt
from ${tableName}
`);
return res.status(200).json({ itemList: itemList });
});
이번 API에서는 body로는 별다른 데이터를 전달해주지 않았고, params 를 통해 조회할 테이블을 특정해주기만 하였다.
테스트를 시행하면
방금 전 3번 API를 테스트하면서 삽입했던 데이터가 정상적으로 출력되는 모습을 볼 수 있다.
여기까지 기본적인 API 구현 테스트를 진행해보았는데, 이 과정들은 전부 제목에 적어놓은 것처럼 RawQuery 를 사용했다. 데이터 정의 언어 DDL 로 테이블을 생성하고, 데이터 조작 언어 DML 로 테이블 목록/데이터 조회 기능을 구현한 것이다.
다만 이 Raw Query 사용 방법도 만능은 아니다.
예시 경우를 들어보자면,
1. 구현한 API의 테이블 컬럼을 수정하게 되었을 때
- 테이블의 컬럼 속성을 수정해야 해서 데이터 속성에 변동이 일어났을 경우, 이 테이블/컬럼을 이용하는 모든 코드들을 수정해야 한다.
2. 사용자가 전달한 데이터를 DB에 직접 요청할 때, 악의적인 쿼리로 서버에 접속하는 등 SQL 인젝션의 취약점을 가지게 된다.
- 이미 널리 알려진 여러 해킹 기법 중 가장 많은 문제를 야기하는 원인으로, 사용자의 데이터를 관리해야 하는 데이터 베이스 코드에서 해킹 취약점을 가진다는 것은 특히 치명적인 문제를 갖고 있다는 말과 동일하다.
이러한 문제들을 해결하기 위해 ORM(Object Realtional Mapping)이 등장하였다.
오늘 진행한 실습처럼 Node.Js 에서 SQL을 직접 작성하는 것이 아니라 JavaScript 로 DB를 조작하는 기법으로, 유지보수성 증가와 OOP 코딩이 가능해지는 방법이다.
다음 글에서 더 다뤄보도록 하겠다.
#7. Prisma Method, 게시글 API 실습 (0) | 2024.12.18 |
---|---|
#6. ORM - Prisma (0) | 2024.12.17 |
#4. SQL 제약 조건 (0) | 2024.12.12 |
#3. SQL 기초 (0) | 2024.12.11 |
#2. 관계형 데이터베이스 RDB, AWS RDS 사용 (3) | 2024.12.04 |