-
왜 프론트에서 바로 DB에 접근하면 안될까?웹개발 2023. 3. 23. 21:27
introduction
처음 풀스택 토이프로젝트를 시작하고 DB를 먼저 작성했다.
이후, 프론트 코드를 작성하며 필요한 DB쿼리들을 바로 받아오면 되겠다는 생각을 했다.
즉, 프론트엔드↔데이터베이스 통신을 생각하고 개발을 진행한 것이다.
대다수의 레퍼런스를 찾아보며 프론트에서는 주소(API)에 접근을 하지 쿼리를 작성하진 않는다는 것을 깨달았다.
CS에서 2계층, 3계층(tier, layer)라고 불리는 구조와 관련된 내용이다.
Concept
가장 간단하고 직관적인 방식으로 유저(클라이언트)는 데이터베이스에 직접 접근할 수 있다.
앞서 말한 프론트엔드 소스코드에서 query문을 작성하는 경우이다.
이 경우를 2-tier 구조라고 부른다. 1. 클라이언트 2. Database 두 가지로 구성된다.
2-tier구조는 보안에 취약하고 유지보수가 어렵다.
위 취약점을 해결하기 위한 구조가 3-tier구조로 클라이언트와 Database사이에 Server/Application layer를 구성해 클라이언트와 Database를 분리한다. 개념에 대해 예제를 통해 알아보자.
Example
프론트엔드에서 이름을 기준으로 유저의 전화번호를 받고 싶다고 하자.
이 경우 프론트엔드의 소스코드는 이렇게 될 것이다. (index.html의 script로 삽입됐다는 가정하에)
var db = openDatabase('mydb', '1.0', 'My database', 2 * 1024 * 1024); db.transaction(function(tx) { tx.executeSql('SELECT user_number FROM users WHERE user_name = ?', [userName], function(tx, results) { var userNumber = results.rows.item(0).user_number; console.log('User number for ' + userName + ' is ' + userNumber); }); });
openDatabase를 통해 DB와 연결하고 Query문을 작성해 직접 Database에 접근해 원하는 데이터를 추출한다. 간단하고 직관적이므로 초기 개발 시에는 이런 식으로 사용을 했다고 한다. 하지만 이 경우 개발자도구를 사용하면 소스코드가 노출이 되며 데이터의 속성에 대한 정보가 노출이 된다. 그리고 이 코드를 인스펙터에서 수정할 경우 데이터베이스에서 직접 조회, 수정, 삭제도 할 수 있다. 따라서 지양해야 하는 방식이다.
이제 3-tier 구조를 보자. 코드가 살짝 복잡해 보이지만 천천히 보면 된다.
//front-end const userNumberOutput = document.getElementById('user-number'); fetch(`/users/${username}`) .then(response => {return response.json();}) .then(data => {userNumberOutput.textContent = data.user_number;}) .catch(error => {console.error(error.message);});
프론트엔드 코드에서는 약속한 /users/{username}이란 api에 접근해 응답을 받아온다. (변수 response)
//back-end const express = require('express'); const bodyParser = require('body-parser'); const sqlite3 = require('sqlite3').verbose(); const app = express(); app.use(bodyParser.json()); const db = new sqlite3.Database('mydb.sqlite'); app.get('/users/:username', (req, res) => { const username = req.params.username; const query = 'SELECT user_number FROM users WHERE user_name = ?'; db.get(query, [username], (err, row) => { if (err) { console.error(err.message); res.status(500).send('Internal server error'); } else if (row) { res.send({ user_number: row.user_number }); } }); }); // Start the server const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server listening on port ${port}`); });
백엔드코드에서는 express를 이용해 /users/{username}이란 url에 get method로 접근할 때 어떻게 할지에 대해 정할 수 있다. (line12-24) 그리고 서버는 항상 열려있어야 하므로 /users/{username}:3000을 listen상태로 전환한다. (line27-30)
우리는 /users/{username}이란 url에 접근한다면 database에서 username과 일치하는 user의 전화번호를 돌려줄 것이므로 해당 문을 query로 작성해 준다.(line14) 이후 db에 query를 넘겨주고 에러가 발생한다면 /users/{username}으로 접근한 프론트엔드는 err를 전달받게 되고 성공한다면 user의 전화번호를 받게 된다.
이런 식으로 기존 2 tier보다 기밀성이 유지되고 데이터베이스 손상 걱정이 없는 코드를 작성할 수 있다.
다음 포스트는 Http method에 관해서 작성 할 예정이다.
GET method에 대해 이해 못했더라도 계층 구조에 대한 이해만 했다면 본문 목표는 달성... ㅎㅎ
본문에 틀린 점이 있다면 지적해주시면 감사하겠습니다.
'웹개발' 카테고리의 다른 글
갑자기 웹개발을 시작하게 되버린 건에 대하여... (0) 2023.01.28