1. Node.js 웹 서버 기초: Hello, World!
Node.js 개발의 첫걸음으로, 간단한 웹 서버를 만들어 보겠습니다. 이를 통해 Node.js의 기본 동작 원리와 서버 생성 과정을 이해할 수 있습니다.
1.1. 기본 개념: 웹 서버의 역할
웹 서버는 클라이언트(예: 웹 브라우저)의 HTTP 요청을 받아 적절한 응답을 반환하는 프로그램입니다. Node.js는 비동기 I/O 모델을 사용하여 높은 성능과 확장성을 제공하며, 많은 동시 연결을 효율적으로 처리할 수 있습니다.
1.2. http
모듈: 기본 웹 서버 만들기
Node.js는 내장 http
모듈을 제공하여 웹 서버를 쉽게 구축할 수 있도록 지원합니다. 가장 기본적인 "Hello, World!" 웹 서버를 만들어 보겠습니다.
// http 모듈 불러오기
const http = require('http');
// 포트 번호 설정
const PORT = 3000;
// HTTP 서버 생성
const server = http.createServer((req, res) => {
// 응답 헤더 설정 (상태 코드 200, Content-Type: text/plain)
res.writeHead(200, { 'Content-Type': 'text/plain' });
// 클라이언트에게 보낼 메시지 작성
res.end('안녕하세요! 이것은 Node.js로 만든 기본 웹 서버입니다.\n');
});
// 서버 실행 및 리스닝 시작
server.listen(PORT, () => {
console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다.`);
});
코드 해설:
require('http')
: Node.js의 내장http
모듈을 불러옵니다.http.createServer((req, res) => { ... })
: HTTP 서버 객체를 생성합니다.createServer
메서드는 콜백 함수를 인자로 받으며, 이 콜백 함수는 클라이언트의 요청이 있을 때마다 호출됩니다.req
: 클라이언트의 요청(request) 정보를 담고 있는 객체입니다.res
: 클라이언트에게 보낼 응답(response)을 설정하는 객체입니다.
res.writeHead(200, { 'Content-Type': 'text/plain' })
: 응답 헤더를 설정합니다. 상태 코드 200 (OK)과 콘텐츠 타입을 "text/plain"으로 지정합니다.res.end('안녕하세요! ...')
: 클라이언트에게 보낼 메시지를 작성하고 응답을 종료합니다.server.listen(PORT, () => { ... })
: 서버가 지정된 포트(3000)에서 요청을 수신하도록 설정합니다. 서버가 시작되면 콜백 함수가 실행되어 콘솔에 메시지를 출력합니다.
1.3. http
모듈 활용: 다양한 응답 처리
단순한 텍스트 응답 외에도 HTML, URL에 따른 분기 처리 등 다양한 응답을 처리하는 예제를 살펴봅니다.
예제 1: HTML 응답하기
const http = require('http');
const PORT = 3000;
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' }); // HTML 컨텐츠 타입
res.end('<h1>안녕하세요!</h1><p>Node.js 웹 서버입니다.</p>');
});
server.listen(PORT, () => {
console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다.`);
});
예제 2: 요청 URL에 따라 다른 응답하기
const http = require('http');
const PORT = 3000;
const server = http.createServer((req, res) => {
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('홈페이지입니다.');
} else if (req.url === '/about') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('About 페이지입니다.');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('페이지를 찾을 수 없습니다.');
}
});
server.listen(PORT, () => {
console.log(`서버가 http://localhost:${PORT} 에서 실행 중입니다.`);
});
코드 해설:
res.writeHead(200, { 'Content-Type': 'text/html' })
: 응답의 Content-Type을 'text/html'로 설정하여 브라우저가 HTML로 렌더링하도록 합니다.req.url
: 요청 URL을 확인하여 조건부로 다른 응답을 제공합니다. 이를 통해 간단한 라우팅을 구현할 수 있습니다.
1.4. 서버 테스트 및 확인
위 코드를 server.js
와 같은 파일로 저장한 후, 터미널에서 node server.js
명령어를 실행하여 서버를 구동합니다. 웹 브라우저에서 http://localhost:3000
, http://localhost:3000/about
등에 접속하여 결과를 확인해 봅니다.
2. Express.js로 RESTful API 구축하기
이번에는 Node.js의 대표적인 웹 프레임워크인 Express.js를 사용하여 RESTful API를 구축하는 방법을 단계별로 살펴보겠습니다.
2.1. RESTful API: 개념 및 특징
REST(Representational State Transfer)는 웹 서비스 설계를 위한 아키텍처 스타일입니다. RESTful API는 HTTP 프로토콜을 기반으로 하며, 자원(Resource)을 URI로 표현하고, HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 자원을 조작합니다.
RESTful API의 주요 특징:
- 자원(Resource) 중심: 모든 것을 자원으로 간주하고 URI를 통해 자원을 식별합니다. (예:
/users
,/users/1
) - HTTP 메서드 활용: HTTP 메서드를 사용하여 자원에 대한 CRUD(Create, Read, Update, Delete) 작업을 수행합니다.
- 상태 비저장성(Stateless): 서버는 클라이언트의 상태 정보를 저장하지 않습니다. 각 요청은 독립적이며, 필요한 모든 정보는 요청에 포함되어야 합니다.
2.2. Express.js: Node.js 웹 프레임워크
Express.js는 Node.js를 위한 간결하고 유연한 웹 애플리케이션 프레임워크입니다. 라우팅, 미들웨어, 템플릿 엔진 등 웹 애플리케이션 개발에 필요한 다양한 기능을 제공하여 개발 생산성을 높여줍니다.
2.3. Express.js로 RESTful API 개발: 단계별 가이드
본격적으로 Express.js를 사용하여 CRUD(Create, Read, Update, Delete) 작업을 수행하는 RESTful API를 구축해 보겠습니다.
2.3.1. 프로젝트 초기화 및 패키지 설치
먼저 프로젝트 폴더를 생성하고, npm init -y
명령어를 사용하여 package.json
파일을 생성합니다.
mkdir my-restful-api
cd my-restful-api
npm init -y
이어서 필요한 패키지(Express.js, body-parser, cors)를 설치합니다.
npm install express body-parser cors
express
: Express.js 프레임워크body-parser
: 요청 본문(request body)을 파싱하여req.body
객체로 만들어주는 미들웨어 (JSON 요청 처리에 필수)cors
: CORS(Cross-Origin Resource Sharing)를 활성화하는 미들웨어 (다른 도메인에서의 요청을 허용할 때 필요)
2.3.2. 기본 서버 구조 작성 (server.js
)
server.js
파일을 생성하고 아래와 같이 Express.js를 사용한 기본 서버 코드를 작성합니다.
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
// body-parser를 사용하여 JSON 요청 본문을 파싱
app.use(bodyParser.json());
// CORS 활성화
app.use(cors());
// 임시 데이터 저장소 (실제 애플리케이션에서는 데이터베이스 사용)
let items = [];
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
코드 해설:
require(...)
: 필요한 모듈들을 불러옵니다.app = express()
: Express 애플리케이션 객체를 생성합니다.app.use(...)
: 미들웨어를 애플리케이션에 추가합니다.bodyParser.json()
: JSON 형식의 요청 본문을 파싱하여req.body
객체에 저장합니다.cors()
: CORS를 활성화합니다.
let items = [];
: 데이터를 임시로 저장할 배열을 선언합니다. (실제 서비스에서는 데이터베이스를 사용합니다.)app.listen(PORT, ...)
: 서버를 시작하고 지정된 포트에서 요청을 수신합니다.
2.3.3. 라우트 정의: CRUD 기능 구현
이제 RESTful API의 핵심인 CRUD 기능을 위한 라우트를 정의합니다.
// ... (기존 코드) ...
// GET /items: 모든 아이템 조회
app.get('/items', (req, res) => {
res.json(items);
});
// POST /items: 새로운 아이템 추가
app.post('/items', (req, res) => {
const newItem = req.body; // 요청 본문에서 새 아이템 데이터 가져오기
// 간단한 유효성 검사: 이름 필수
if (!newItem.name) {
return res.status(400).json({ error: 'Name is required' });
}
items.push(newItem);
res.status(201).json(newItem); // 201 Created 상태 코드와 함께 새 아이템 반환
});
// PUT /items/:id: 특정 아이템 수정
app.put('/items/:id', (req, res) => {
const id = parseInt(req.params.id); // URL 파라미터에서 ID 가져오기
const updatedItem = req.body;
// 아이템 존재 여부 확인
if (!items[id]) {
return res.status(404).json({ error: 'Item not found' });
}
items[id] = updatedItem;
res.json(updatedItem);
});
// DELETE /items/:id: 특정 아이템 삭제
app.delete('/items/:id', (req, res) => {
const id = parseInt(req.params.id);
// 아이템 존재 여부 확인
if (!items[id]) {
return res.status(404).json({ error: 'Item not found' });
}
items.splice(id, 1); // 해당 인덱스의 아이템 제거
res.status(204).send(); // 204 No Content (삭제 성공)
});
코드 해설:
app.get('/items', ...)
:GET /items
요청을 처리하는 라우트를 정의합니다. 모든 아이템 목록을 JSON 형식으로 응답합니다.app.post('/items', ...)
:POST /items
요청을 처리합니다. 요청 본문에서 새 아이템 정보를 가져와items
배열에 추가하고, 성공 응답(201 Created)과 함께 추가된 아이템을 반환합니다.app.put('/items/:id', ...)
:PUT /items/:id
요청을 처리합니다. URL 파라미터:id
를 통해 수정할 아이템의 인덱스를 식별하고, 요청 본문의 데이터로 아이템 정보를 업데이트합니다.app.delete('/items/:id', ...)
:DELETE /items/:id
요청을 처리합니다. URL 파라미터:id
를 통해 삭제할 아이템의 인덱스를 식별하고,splice()
메서드로 해당 아이템을 제거합니다.req.params.id
: URL 경로에서:id
에 해당하는 파라미터 값을 가져옵니다.req.body
: 요청 본문에 포함된 데이터(JSON)를 담고 있는 객체입니다.res.json(...)
: JSON 형식의 응답을 전송합니다.res.status(...)
: HTTP 상태 코드를 설정합니다.
2.3.4. 추가 라우트: 검색 및 상세 조회
기본 CRUD 기능 외에도 검색, 상세 조회와 같은 유용한 라우트를 추가해 보겠습니다.
// ... (기존 코드) ...
// GET /items/search?name=:name - 이름으로 아이템 검색
app.get('/items/search', (req, res) => {
const name = req.query.name;
const filteredItems = items.filter(item => item.name.includes(name));
res.json(filteredItems);
});
// GET /items/:id - ID로 특정 아이템 조회
app.get('/items/:id', (req, res) => {
const id = parseInt(req.params.id);
if (!items[id]) {
return res.status(404).json({ error: 'Item not found' });
}
res.json(items[id]);
});
// 예시 데이터에 카테고리 속성 추가 (테스트용)
items = [
{ name: "사과", category: "과일" },
{ name: "바나나", category: "과일" },
{ name: "당근", category: "채소" },
];
// GET /items/category/:category - 카테고리별 아이템 조회
app.get('/items/category/:category', (req, res) => {
const category = req.params.category;
const filteredItems = items.filter(item => item.category === category);
res.json(filteredItems);
});
코드 해설:
app.get('/items/search?name=...', ...)
:GET /items/search?name=검색어
요청을 처리합니다.req.query.name
을 통해 쿼리 파라미터name
의 값을 가져오고,filter()
메서드를 사용하여 해당 이름을 포함하는 아이템만 필터링하여 반환합니다.app.get('/items/:id', ...)
:GET /items/:id
요청을 처리합니다. URL 파라미터:id
를 통해 조회할 아이템의 인덱스를 식별하고, 해당 아이템 정보를 JSON 형식으로 반환합니다.app.get('/items/category/:category', ...)
:GET /items/category/:category
요청을 처리합니다. URL 파라미터:category
를 통해 조회할 아이템의 카테고리를 식별하고,filter()
메서드를 사용하여 해당 카테고리에 속하는 아이템만 필터링하여 반환합니다.
2.3.5. API 테스트 및 검증
node server.js
명령어로 서버를 시작하고, Postman, Insomnia, curl
과 같은 도구를 사용하여 API를 테스트합니다. 다양한 요청(GET, POST, PUT, DELETE, 검색, 상세 조회)을 보내고, 응답 코드와 데이터가 예상대로 반환되는지 확인합니다.
3. Node.js와 데이터베이스 연동: 데이터 영속성 확보
지금까지는 items
배열에 임시로 데이터를 저장했습니다. 실제 애플리케이션에서는 데이터베이스를 사용하여 데이터를 영구적으로 저장하고 관리해야 합니다. 이번 섹션에서는 Node.js 애플리케이션에서 데이터베이스를 사용하는 방법을 살펴보겠습니다.
3.1. 데이터베이스 종류: 관계형(RDBMS) vs. NoSQL
Node.js는 다양한 관계형 데이터베이스(RDBMS)와 NoSQL 데이터베이스를 지원합니다.
- 관계형 데이터베이스 (RDBMS): MySQL, PostgreSQL, SQL Server 등. 데이터를 테이블 형태로 저장하고 SQL을 사용하여 데이터를 쿼리합니다. 정형화된 데이터, 복잡한 쿼리, 트랜잭션 처리에 적합합니다.
- NoSQL 데이터베이스: MongoDB, Redis, Cassandra 등. 스키마가 유연하고, 대규모 데이터 처리에 적합합니다. 비정형 데이터, 빠른 쓰기/읽기, 수평적 확장이 필요한 경우에 유용합니다.
3.2. MySQL 연동: RDBMS 활용
MySQL을 예로 들어 Node.js에서 관계형 데이터베이스를 사용하는 방법을 알아보겠습니다.
3.2.1. mysql
패키지 설치
npm install mysql
3.2.2. MySQL 연결 및 쿼리 실행
const mysql = require('mysql');
// MySQL 연결 설정
const connection = mysql.createConnection({
host: 'localhost', // 데이터베이스 호스트
user: 'your_mysql_username', // MySQL 사용자 이름
password: 'your_mysql_password', // MySQL 비밀번호
database: 'your_database_name' // 데이터베이스 이름
});
// 연결
connection.connect((err) => {
if (err) {
console.error('Error connecting to MySQL:', err);
return;
}
console.log('Connected to MySQL database!');
});
// 사용자 추가 쿼리
const newUser = { name: 'John Doe', email: 'john.doe@example.com' };
connection.query('INSERT INTO users SET ?', newUser, (error, results) => {
if (error) {
console.error('Error inserting user:', error);
return;
}
console.log('User inserted successfully! ID:', results.insertId);
});
// 모든 사용자 조회 쿼리
connection.query('SELECT * FROM users', (error, results) => {
if (error) {
console.error('Error fetching users:', error);
return;
}
console.log('Users:', results);
});
// 특정 사용자 조회
const userId = 1;
connection.query('SELECT * FROM users WHERE id = ?', [userId], (error, results) => {
if (error) {
console.error('Error fetching user:', error);
return;
}
if (results.length > 0) {
console.log('User found:', results[0]);
} else {
console.log('User not found.');
}
});
// 사용자 정보 업데이트
const userIdToUpdate = 1;
const updatedUser = { name: 'Updated Name', email: 'updated.email@example.com' };
connection.query('UPDATE users SET ? WHERE id = ?', [updatedUser, userIdToUpdate], (error, results) => {
if (error) {
console.error('Error updating user:', error);
return;
}
console.log('User updated successfully!');
});
// 사용자 삭제
const userIdToDelete = 1;
connection.query('DELETE FROM users WHERE id = ?', [userIdToDelete], (error, results) => {
if (error) {
console.error('Error deleting user:', error);
return;
}
console.log('User deleted successfully!');
});
// 연결 종료 (더 이상 쿼리를 실행하지 않을 때)
// connection.end();
코드 해설:
mysql.createConnection(...)
: MySQL 연결 객체를 생성합니다. 연결 정보를 인자로 전달합니다.connection.connect(...)
: 데이터베이스에 연결합니다.connection.query(...)
: SQL 쿼리를 실행합니다.- 첫 번째 인자: 실행할 SQL 쿼리 문자열
- 두 번째 인자 (선택): 쿼리에 사용할 파라미터 값 (예:
newUser
,userId
). Prepared Statement를 사용하여 SQL Injection 공격을 방지합니다. - 세 번째 인자: 콜백 함수. 쿼리 실행 후 호출됩니다.
error
: 에러 객체 (쿼리 실행 중 에러가 발생한 경우)results
: 쿼리 실행 결과
connection.end()
: 데이터베이스 연결을 종료합니다.
3.3. MongoDB 연동: NoSQL 활용
이번에는 NoSQL 데이터베이스 중 가장 널리 사용되는 MongoDB를 Node.js와 연동하는 방법을 살펴보겠습니다.
3.3.1. mongoose
패키지 설치
MongoDB를 편리하게 사용하기 위해 ODM(Object Document Mapper) 라이브러리인 mongoose
를 설치합니다.
npm install mongoose
3.3.2. MongoDB 연결, 스키마 정의, CRUD 작업
const mongoose = require('mongoose');
// MongoDB 연결
mongoose.connect('mongodb://localhost:27017/your_database_name', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('Connected to MongoDB!'))
.catch(err => console.error('Error connecting to MongoDB:', err));
// 사용자 스키마 정의
const userSchema = new mongoose.Schema({
name: { type: String, required: true }, // 이름 (필수)
email: String,
age: Number
});
// 사용자 모델 생성
const User = mongoose.model('User', userSchema);
// 새로운 사용자 생성 및 저장
const newUser = new User({ name: 'Jane Smith', email: 'jane.smith@example.com', age: 30 });
newUser.save()
.then(user => console.log('User saved:', user))
.catch(err => console.error('Error saving user:', err));
// 모든 사용자 조회
User.find({})
.then(users => console.log('Users:', users))
.catch(err => console.error('Error fetching users:', err));
// 특정 조건 사용자 조회 (나이가 25세 이상)
User.find({ age: { $gte: 25 } })
.then(users => console.log('Users (age >= 25):', users))
.catch(err => console.error('Error fetching users:', err));
// 사용자 업데이트 (이름으로 검색 후 이메일 변경)
User.findOneAndUpdate({ name: 'Jane Smith' }, { email: 'updated.email@example.com' }, { new: true })
.then(updatedUser => {
if (updatedUser) {
console.log('User updated:', updatedUser);
} else {
console.log('User not found.');
}
})
.catch(err => console.error('Error updating user:', err));
// 사용자 삭제 (이름으로 검색 후 삭제)
User.findOneAndDelete({ name: 'Jane Smith' })
.then(deletedUser => {
if (deletedUser) {
console.log('User deleted:', deletedUser);
} else {
console.log('User not found.');
}
})
.catch(err => console.error('Error deleting user:', err));
코드 해설:
mongoose.connect(...)
: MongoDB에 연결합니다. 연결 문자열을 인자로 전달합니다.mongoose.Schema(...)
: 데이터의 구조를 정의하는 스키마를 생성합니다.mongoose.model('User', userSchema)
: 스키마를 기반으로 모델을 생성합니다. 모델은 MongoDB 컬렉션과 상호 작용하는 데 사용됩니다.new User(...)
: 모델 인스턴스(문서)를 생성합니다.newUser.save()
: 문서를 데이터베이스에 저장합니다.User.find(...)
: 쿼리를 실행하여 문서를 조회합니다.User.find({})
: 모든 문서를 조회합니다.User.find({ age: { $gte: 25 } })
:age
필드가 25 이상인 문서를 조회합니다.$gte
는 "greater than or equal to"를 의미합니다.
User.findOneAndUpdate(...)
: 조건에 맞는 문서를 찾아 업데이트합니다.{ new: true }
옵션은 업데이트된 문서를 반환하도록 합니다.User.findOneAndDelete(...)
: 조건에 맞는 문서를 찾아 삭제합니다.
3.4. 데이터베이스 성능 최적화: 핵심 전략
데이터베이스를 사용할 때는 성능 최적화를 고려하는 것이 중요합니다.
- 커넥션 풀링(Connection Pooling): 데이터베이스 연결을 생성하고 닫는 작업은 비용이 많이 듭니다. 커넥션 풀링은 연결을 재사용하여 성능을 향상시킵니다. 대부분의 데이터베이스 드라이버는 커넥션 풀링을 지원합니다.
- 인덱싱(Indexing): 자주 사용되는 쿼리의 성능을 향상시키기 위해 적절한 인덱스를 생성해야 합니다. 예를 들어, 사용자 이름으로 사용자를 자주 조회하는 경우,
name
필드에 인덱스를 생성하는 것이 좋습니다. - 쿼리 최적화: 비효율적인 쿼리는 성능 저하의 원인이 됩니다. 쿼리를 분석하고 최적화하여 성능을 개선해야 합니다.
결론: Node.js로 효율적인 웹 애플리케이션 개발
본 가이드를 통해 Node.js를 사용하여 웹 서버를 구축하고, Express.js를 활용하여 RESTful API를 개발하고, 데이터베이스를 연동하는 방법을 단계별로 살펴보았습니다. 이러한 기본 지식을 바탕으로 더 복잡하고 기능이 풍부한 웹 애플리케이션을 개발할 수 있습니다. Node.js의 강력한 기능과 생태계를 활용하여 효율적이고 확장 가능한 웹 애플리케이션을 구축해 보시기 바랍니다.
'프로그래밍 > Node.js' 카테고리의 다른 글
Node.js: 서버 개발의 새로운 패러다임 - 심층 분석 (0) | 2025.02.18 |
---|---|
Node.js 웹 개발: Express.js와 Koa.js 프레임워크 비교 (0) | 2025.02.18 |
Node.js 핵심 정복: 주요 모듈 완벽 가이드 (0) | 2025.02.18 |
Node.js 정복을 위한 세 가지 열쇠: 이벤트 기반 프로그래밍, 비동기 I/O, 모듈 시스템 (0) | 2025.02.18 |
Node.js 정복 가이드: 설치부터 NPM 활용까지, 서버 개발 첫걸음 (0) | 2025.02.17 |