백엔드의 처리 과정을 크게 보자면 다음과 같다.
요청 → 요청 데이터 처리 → DB 조회 → 비즈니스 로직 → 응답
이 과정으로 간단하게 CRUD를 진행해보고자 한다.
pages/api 디렉토리나 app/api/<도메인>/route.js 내에 파일을 만들어 API 라우트를 설정할 수 있다. Next.js 13버전 이후부터는 app/api 에 생성하는 것을 권장한다.
이 파일은 서버 측에서 실행되고, 프론트엔드가 요청을 보낼 수 있는 엔드포인트 역할을 한다.
/app/api/hello/route.js
export async function GET(request) {
return new Response(JSON.stringify( {message: "Hello, Next.js API!"}),
{
status: 200,
headers: { "Content-Type": "application/json"},
});
}
GET 요청을 이런식으로 생성할 수 있다.
Next.js의 url 구조는 폴더 구조를 따라가기 때문에 원하는대로 바꾸고 싶다면 폴더 구조를 변경해야 한다.
현재 /app/api/hello/route.js 의 위치에 라우터가 구현되어 있다. 그러므로 localhost:3000/api/hello/route.js url에서 확인이 가능하다.

*백엔드에서 api 요청 path는 path의 맨 앞에 api를 붙이는 경우가 많다.(회사 컨벤션 차이가 있긴 할듯)
요청을 받았다면 요청 데이터 정제를 뚝딱한 뒤에 DB에 요청을 할 수 있어야 한다. 이 부분은 익숙한 MySQL로 해보겠다. 데이터베이스는 기본으로 딸려오는 employees 를 활용할 것이다.
cmd
npm install mysql2
/utils/db.connection.js
import mysql from 'mysql2';
// MySQL 연결 설정
const connection = mysql.createConnection({
host: 'localhost', // MySQL 서버 호스트 (로컬에서 실행 시 localhost)
user: 'root', // MySQL 사용자명
password: '02569874a@', // MySQL 비밀번호
database: 'employees', // 사용할 데이터베이스명
});
// MySQL 연결 시도
connection.connect((err) => {
if (err) {
console.error('MySQL 연결 실패:', err);
} else {
console.log('MySQL 연결 성공');
}
});
// 연결 객체를 외부에서 사용할 수 있게 export
export default connection;
npm에서 mysql2 패키지를 install한 후, db 연결을 할 파일을 만든다. 뭐 dbconfig든 dbconnection이든 dbutil이든 파일이라 디렉토리명 선택은 잘 모르겠다. 아무튼 이렇게 자신의 계정을 통해 DB와 연결한 후 외부에서 사용할 수 있도록 객체를 export해준다.
기본만 하기 때문에 이렇게 했지만 사실 커넥션 풀을 사용하는게 좋다.
이전에 route.js에서 localhost:3000/api/hello 로 들어오는 GET 요청을 받을 수 있었다. 이 요청을 받았을 때 employees DB의 departments 테이블을 조회해서 반환해볼 것이다.
/app/api/hello/route.js
//connection을 import
import connection from '../../../utils/db.connection';
//get 요청 함수
export async function GET(request) {
return new Promise((resolve, reject) => {
// MySQL에서 모든 사용자 정보 조회
connection.query('SELECT * FROM departments', (err, results) => {
if (err) {
reject(err);
} else {
resolve(
new Response(
JSON.stringify(results), // JSON 형식으로 결과 반환
{
status: 200,
headers: { 'Content-Type': 'application/json' },
}
)
);
}
});
});
}
코드를 설명하자면 다음과 같다.
Promise
Promise 객체는 우선 비동기 작업을 동기처럼 처리하는 구조로 만들어준다. connection.query 즉, DB에 요청을 보내고 받는 작업은 비동기로 처리될 경우 매우 위험하다. Promise는 실제로 동기적으로 작동하지는 않지만 작업이 끝날 때까지 다른 작업이 대기하도록 하여 동기적인 것 처럼 처리한다.
resolve, reject
resolve와 reject는 각각 작업이 정상적으로 끝났을 때와 실패했을 때 호출된다. Promise 객체의 작업 결과를 통해 상태를 변경한다.
query 작업이 끝난 후 결과를 통해 이후 작업을 콜백함수로 처리할 것이다. 이 코드에선 정상 처리될 경우 결과물인 json 객체를 그대로 반환하고 아님 오류를 발생시킨다.
잡담
역시나 자바스크립트의 특징인 콜백함수 때문에 들여쓰기가 꽤나 깊다. 보기엔 안좋지만 콜백함수는 비동기 작업이 완료된 후 실행될 함수를 미리 전달한다. 그러므로 비동기 작업 안에서 작업 결과에 대해 다른 처리를 덧붙일 수 있다는 장점이 있고, 비동기 작업의 마지막에 위치하기 때문에 비동기 작업의 흐름을 제어하는 역할을 하기도 한다.
콜백지옥;;;
결과

localhost:3000/api/hello 에서 확인할 수 있다.
나머지 create, update, delete도 구현해보자. 번호가 d999인 부서로 진행할 예정이다. 웹에서 get 이외의 쿼리들은 요청에 한계가 있다. 그러므로 postman을 사용한다.
/app/api/hello/route.js
export async function POST(request) {
const data = await request.json();
const { deptNo, deptName } = data;
return new Promise((resolve, reject) => {
connection.query(
'INSERT INTO departments (dept_no, dept_name) VALUES (?, ?)',
[deptNo, deptName],
(err, results) => {
if (err) {
reject(err);
} else {
resolve(
new Response(
JSON.stringify({deptNo, deptName}),
{ status: 201, headers: { 'Content-Type': 'application/json' } }
)
);}
});
});
}
Request
{
"deptNo":"d999",
"deptName":"newDept"
}
Response


/app/api/hello/route.js
export async function PUT(request) {
const data = await request.json();
const { deptNo, deptName } = data;
return new Promise((resolve, reject) => {
connection.query(
'UPDATE departments SET dept_name = ? WHERE dept_no = ?',
[deptName, deptNo],
(err, results) => {
if (err) {
reject(err);
} else {
resolve(
new Response(
JSON.stringify({ deptNo, deptName }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)
);
}
}
);
});
}
Request
{
"deptNo":"d999",
"deptName":"updatedDept"
}
Response


/app/api/hello/route.js
export async function DELETE(request) {
const { deptNo } = await request.json();
return new Promise((resolve, reject) => {
connection.query(
'DELETE FROM departments WHERE dept_no = ?',
[deptNo],
(err, results) => {
if (err) {
reject(err);
} else {
resolve(
new Response(
JSON.stringify({ message: 'deleted successfully' }),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
)
);
}
}
);
});
}
Request
{
"deptNo":"d999"
}
Response

Next.js의 CRUD를 경험해보았다. 클린 코드나 컨벤션을 신경쓰기보다는 되는대로 했다. 그래서 정확히 파악하지 못한 부분이 많을수도 있다. 그래도 느낀 점은 작성해야하니 매우 주관적으로 작성해보려고 한다!
장점
Next.js가 갖는 기본적인 장점이 있다. 필요한 시점에 SSR과 CSR을 혼용할 수 있다는 점이다.
이런 느낌이면 빠른 개발이 가능할 것 같았다.
사실 백엔드를 기능 구현만 하는거면 CRUD 구현이 많은데 이걸 빨리할 수 있을 것 같았다. 작은 프로젝트에서 매우 유용할 것 같다!
단점
프로젝트 규모나 복잡성이 증가했을 때 문제가 있을 듯 했음
Spring을 하다가 이걸 하니 분리가 부족하다는 느낌이 들었고 쿼리가 복잡해지거나 처리가 늘어나면 좀 dirty~해질 것 같았다. 특히 여러명이 작업하는 실무 환경에서는 더 크리티컬할 것이다.
구조적인 부분이 부족해 보였음
다른 프레임워크에서 신경을 쓰는 운반 객체나 역할 분할 등이 어려웠다. 적다보니 위와 비슷한 느낌이다. 간단하게 말하면 유지보수에 어려울 것 같다는 뜻이었다.
백엔드 유틸리티 과거 회귀
DB 매핑, 유틸리티 등이 좀 부족하다는 느낌을 받았다. Spring이나 Django 같은 다른 프레임워크들은 나름 자기딴에 백엔드적으로 편의성을 늘리고 안정성을 높이고 있다. 관련 인터페이스들도 많은 편이다.
예시로 Spring은 mybatis나 jpa로 DB 매핑을 좀 더 편하게 만들었고, Django는 추상화를 많이 시켜놓은 다양한 유형의 Model이나 Serializer를 제공해 기본 템플릿을 늘려놨다.
근데 원시적인 형태라고 느꼈다.