← 블로그로 돌아가기

JSON API와 REST 서비스: 현대 웹 개발의 핵심

JSON API와 REST 서비스의 모든 것을 배워보세요. RESTful 설계 원칙, 실용적인 예제, 모범 사례까지 완벽 정리.

Big JSON Team13분 소요web-development
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

13 분 읽기

# JSON API와 REST 서비스: 현대 웹 개발의 핵심

JSON API와 REST는 현대 웹 애플리케이션의 표준입니다. 이 가이드에서 RESTful API 설계와 JSON 사용법을 완벽히 마스터하세요.

REST API란?

REST (Representational State Transfer)는 웹 서비스를 설계하는 아키텍처 스타일입니다.

REST의 핵심 원칙

  • 클라이언트-서버 분리: 프론트엔드와 백엔드가 독립적
  • 무상태 (Stateless): 각 요청은 독립적
  • 캐시 가능: 응답을 캐시할 수 있음
  • 계층화된 시스템: 여러 계층 구조 가능
  • 균일한 인터페이스: 일관된 API 설계
  • 코드 온 디맨드 (선택): 필요시 코드 전송
  • 왜 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 응답을 테스트해보세요!

    Share:

    관련 글

    Read in English