JSON API와 REST 서비스: 현대 웹 개발의 핵심
JSON API와 REST 서비스의 모든 것을 배워보세요. RESTful 설계 원칙, 실용적인 예제, 모범 사례까지 완벽 정리.
Big JSON Team
• Technical WriterExpert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.
# JSON API와 REST 서비스: 현대 웹 개발의 핵심
JSON API와 REST는 현대 웹 애플리케이션의 표준입니다. 이 가이드에서 RESTful API 설계와 JSON 사용법을 완벽히 마스터하세요.
REST API란?
REST (Representational State Transfer)는 웹 서비스를 설계하는 아키텍처 스타일입니다.
REST의 핵심 원칙
왜 JSON과 REST를 함께 사용하나요?
JSON의 장점
- ✅ 경량: XML보다 가볍고 빠름
- ✅ 읽기 쉬움: 사람과 기계 모두 이해하기 쉬움
- ✅ JavaScript 통합: 웹과 완벽한 호환
- ✅ 언어 독립적: 모든 언어에서 지원
- ✅ 파싱 빠름: 처리 속도가 빠름
HTTP 메서드와 CRUD
| HTTP 메서드 | CRUD | 설명 |
|------------|------|------|
| GET | Read | 데이터 조회 |
| POST | Create | 새 데이터 생성 |
| PUT | Update | 전체 데이터 업데이트 |
| PATCH | Update | 일부 데이터 업데이트 |
| DELETE | Delete | 데이터 삭제 |
RESTful API 설계
URL 설계 원칙
좋은 예:GET /api/users // 모든 사용자 조회
GET /api/users/123 // 특정 사용자 조회
POST /api/users // 새 사용자 생성
PUT /api/users/123 // 사용자 전체 업데이트
PATCH /api/users/123 // 사용자 부분 업데이트
DELETE /api/users/123 // 사용자 삭제
나쁜 예:
GET /api/getUsers
POST /api/createUser
GET /api/user/delete/123
리소스 이름 규칙
/users, /products/users, not /Users/user-profiles, not /user_profiles/users, not /getUsers중첩 리소스
GET /api/users/123/posts // 사용자의 모든 게시물
GET /api/users/123/posts/456 // 사용자의 특정 게시물
POST /api/users/123/posts // 사용자의 새 게시물
요청과 응답 예제
GET 요청 - 목록 조회
요청:GET /api/users HTTP/1.1
Host: example.com
Accept: application/json
응답:
{
"data": [
{
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
},
{
"id": 2,
"name": "김철수",
"email": "kim@example.com"
}
],
"meta": {
"total": 2,
"page": 1,
"perPage": 10
}
}
GET 요청 - 단일 항목 조회
요청:GET /api/users/1 HTTP/1.1
Host: example.com
Accept: application/json
응답:
{
"id": 1,
"name": "홍길동",
"email": "hong@example.com",
"createdAt": "2026-01-16T10:00:00Z"
}
POST 요청 - 생성
요청:POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json
Accept: application/json
{
"name": "이영희",
"email": "lee@example.com",
"password": "securepass123"
}
응답 (201 Created):
{
"id": 3,
"name": "이영희",
"email": "lee@example.com",
"createdAt": "2026-01-16T11:00:00Z"
}
PUT 요청 - 전체 업데이트
요청:PUT /api/users/3 HTTP/1.1
Host: example.com
Content-Type: application/json
{
"name": "이영희",
"email": "newemail@example.com",
"phone": "010-1234-5678"
}
응답:
{
"id": 3,
"name": "이영희",
"email": "newemail@example.com",
"phone": "010-1234-5678",
"updatedAt": "2026-01-16T12:00:00Z"
}
PATCH 요청 - 부분 업데이트
요청:PATCH /api/users/3 HTTP/1.1
Host: example.com
Content-Type: application/json
{
"email": "updated@example.com"
}
DELETE 요청
요청:DELETE /api/users/3 HTTP/1.1
Host: example.com
응답 (204 No Content):
빈 응답 또는:
{
"message": "사용자가 삭제되었습니다"
}
HTTP 상태 코드
성공 응답 (2xx)
- 200 OK: 요청 성공
- 201 Created: 리소스 생성 성공
- 204 No Content: 성공, 반환 데이터 없음
클라이언트 오류 (4xx)
- 400 Bad Request: 잘못된 요청
- 401 Unauthorized: 인증 필요
- 403 Forbidden: 권한 없음
- 404 Not Found: 리소스 없음
- 422 Unprocessable Entity: 검증 실패
서버 오류 (5xx)
- 500 Internal Server Error: 서버 오류
- 503 Service Unavailable: 서비스 이용 불가
오류 응답 형식
표준 오류 형식
{
"error": {
"status": 400,
"code": "VALIDATION_ERROR",
"message": "요청 데이터가 유효하지 않습니다",
"details": [
{
"field": "email",
"message": "유효한 이메일 주소를 입력하세요"
},
{
"field": "age",
"message": "나이는 0보다 커야 합니다"
}
]
}
}
404 오류
{
"error": {
"status": 404,
"code": "NOT_FOUND",
"message": "사용자를 찾을 수 없습니다",
"resource": "User",
"resourceId": "123"
}
}
페이징
오프셋 기반
요청:GET /api/users?page=2&perPage=10
응답:
{
"data": [...],
"pagination": {
"page": 2,
"perPage": 10,
"total": 100,
"totalPages": 10,
"hasNext": true,
"hasPrev": true
},
"links": {
"first": "/api/users?page=1&perPage=10",
"prev": "/api/users?page=1&perPage=10",
"next": "/api/users?page=3&perPage=10",
"last": "/api/users?page=10&perPage=10"
}
}
커서 기반
{
"data": [...],
"pagination": {
"nextCursor": "eyJpZCI6MjB9",
"hasMore": true
}
}
필터링과 정렬
필터링
GET /api/users?status=active&role=admin
GET /api/users?age[gte]=18&age[lte]=65
GET /api/products?price[min]=1000&price[max]=50000
정렬
GET /api/users?sort=createdAt // 오름차순
GET /api/users?sort=-createdAt // 내림차순
GET /api/users?sort=name,-createdAt // 다중 정렬
필드 선택
GET /api/users?fields=id,name,email
인증과 권한
JWT 토큰
요청 헤더:GET /api/users/me HTTP/1.1
Host: example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
API 키
GET /api/users HTTP/1.1
Host: example.com
X-API-Key: your-api-key-here
로그인 API
요청:POST /api/auth/login HTTP/1.1
Content-Type: application/json
{
"email": "hong@example.com",
"password": "securepass123"
}
응답:
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 3600,
"user": {
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
}
}
Node.js/Express 구현
기본 서버 설정
const express = require('express');
const app = express();
// JSON 파싱 미들웨어
app.use(express.json());
// CORS 설정
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(서버가 포트 ${PORT}에서 실행 중입니다);
});
GET - 목록 조회
const users = [
{ id: 1, name: '홍길동', email: 'hong@example.com' },
{ id: 2, name: '김철수', email: 'kim@example.com' }
];
app.get('/api/users', (req, res) => {
const { page = 1, perPage = 10, sort } = req.query;
// 정렬
let sortedUsers = [...users];
if (sort) {
const [field, order] = sort.startsWith('-')
? [sort.slice(1), -1]
: [sort, 1];
sortedUsers.sort((a, b) =>
order (a[field] > b[field] ? 1 : -1)
);
}
// 페이징
const start = (page - 1) * perPage;
const end = start + parseInt(perPage);
const paginatedUsers = sortedUsers.slice(start, end);
res.json({
data: paginatedUsers,
pagination: {
page: parseInt(page),
perPage: parseInt(perPage),
total: users.length,
totalPages: Math.ceil(users.length / perPage)
}
});
});
GET - 단일 항목
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
return res.status(404).json({
error: {
status: 404,
message: '사용자를 찾을 수 없습니다'
}
});
}
res.json(user);
});
POST - 생성
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
// 검증
if (!name || !email) {
return res.status(400).json({
error: {
status: 400,
message: '이름과 이메일은 필수입니다'
}
});
}
const newUser = {
id: users.length + 1,
name,
email,
createdAt: new Date().toISOString()
};
users.push(newUser);
res.status(201).json(newUser);
});
PUT - 전체 업데이트
app.put('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
error: { message: '사용자를 찾을 수 없습니다' }
});
}
users[index] = {
id: parseInt(req.params.id),
...req.body,
updatedAt: new Date().toISOString()
};
res.json(users[index]);
});
PATCH - 부분 업데이트
app.patch('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
error: { message: '사용자를 찾을 수 없습니다' }
});
}
users[index] = {
...users[index],
...req.body,
updatedAt: new Date().toISOString()
};
res.json(users[index]);
});
DELETE - 삭제
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === parseInt(req.params.id));
if (index === -1) {
return res.status(404).json({
error: { message: '사용자를 찾을 수 없습니다' }
});
}
users.splice(index, 1);
res.status(204).send();
});
클라이언트 사용 예제
Fetch API (JavaScript)
// GET
async function getUsers() {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return data;
}
// POST
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message);
}
return response.json();
}
// 사용
try {
const newUser = await createUser({
name: '홍길동',
email: 'hong@example.com'
});
console.log('생성됨:', newUser);
} catch (error) {
console.error('오류:', error.message);
}
Axios
const axios = require('axios');
// GET
const users = await axios.get('https://api.example.com/users');
// POST
const newUser = await axios.post('https://api.example.com/users', {
name: '홍길동',
email: 'hong@example.com'
});
// 인터셉터로 토큰 추가
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = Bearer ${token};
}
return config;
});
모범 사례
1. 버전 관리
https://api.example.com/v1/users
https://api.example.com/v2/users
2. HATEOAS
응답에 관련 링크 포함:
{
"id": 1,
"name": "홍길동",
"links": {
"self": "/api/users/1",
"posts": "/api/users/1/posts",
"profile": "/api/users/1/profile"
}
}
3. 속도 제한
응답 헤더에 정보 포함:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642348800
4. 캐싱
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
5. 압축
Content-Encoding: gzip
JSON API 스펙
JSON API (jsonapi.org)는 JSON 기반 API의 표준 스펙입니다.
응답 형식:{
"data": {
"type": "users",
"id": "1",
"attributes": {
"name": "홍길동",
"email": "hong@example.com"
},
"relationships": {
"posts": {
"links": {
"related": "/users/1/posts"
}
}
}
},
"included": []
}
결론
JSON API와 REST는 현대 웹 개발의 기초입니다. RESTful 원칙을 따르고, 일관된 JSON 형식을 사용하며, 적절한 HTTP 상태 코드를 반환하는 것이 핵심입니다.
핵심 요약:- ✅ RESTful 원칙 준수
- ✅ 일관된 URL 구조
- ✅ 적절한 HTTP 메서드 사용
- ✅ 명확한 오류 메시지
- ✅ 페이징과 필터링 지원
지금 바로 JSON Simplify에서 API 응답을 테스트해보세요!
관련 글
JSON 파일: 구조와 사용법 완벽 가이드
JSON 파일에 대한 종합 가이드 - .json 확장자, MIME 타입, 구조 및 JSON 파일을 효과적으로 생성, 열기, 사용하는 방법을 배워보세요.
JavaScript와 JSON: 완벽한 가이드 2026
JavaScript에서 JSON을 마스터하세요. JSON.parse(), JSON.stringify(), 오류 처리, fetch API 및 모범 사례에 대한 완벽한 가이드입니다.
대용량 JSON 다루기: 성능 최적화와 실용 기법
기가바이트 크기의 JSON 파일을 효율적으로 처리하는 방법을 배워보세요. 스트리밍, 청크 처리, 메모리 최적화까지 완벽 정리.