API ์„ค๊ณ„ ์›์น™

API ์„ค๊ณ„ ์›์น™

ํ† ํ”ฝ: Programming ๋ ˆ์Šจ: 13 of 16 ์„ ์ˆ˜ ์ง€์‹: ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ, HTTP ๊ธฐ์ดˆ, JSON/XML ํ˜•์‹ ๋ชฉํ‘œ: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ๋ชจ๋“ˆ, ์›น ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ๋ช…ํ™•ํ•˜๊ณ  ์ผ๊ด€๋œ ์œ ์ง€๋ณด์ˆ˜ ๊ฐ€๋Šฅํ•œ API ์„ค๊ณ„ ๋ฐฉ๋ฒ• ํ•™์Šต

์†Œ๊ฐœ

API(Application Programming Interface)๋Š” ์†Œํ”„ํŠธ์›จ์–ด ๊ตฌ์„ฑ ์š”์†Œ๊ฐ€ ํ†ต์‹ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ •์˜ํ•˜๋Š” ๊ณ„์•ฝ์ž…๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•จ์ˆ˜, REST ์—”๋“œํฌ์ธํŠธ ๋˜๋Š” ์‹œ์Šคํ…œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์„ค๊ณ„ํ•˜๋“ , ์ข‹์€ API ์„ค๊ณ„๋Š” ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๋” ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•˜๊ณ , ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ณ , ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

์ž˜๋ชป๋œ API ์„ค๊ณ„๋Š” ๋‹ค์Œ์„ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค: - ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋Š” ํ˜ผ๋ž€์Šค๋Ÿฌ์šด ์‚ฌ์šฉ์ž - ์ž‘์€ ๋ณ€๊ฒฝ์—๋„ ๊นจ์ง€๋Š” ์ทจ์•ฝํ•œ ์ฝ”๋“œ - ๋ถˆ๋ช…ํ™•ํ•œ ๋ฌธ์„œ๋กœ ์ธํ•œ ์ง€์› ๋ถ€๋‹ด - ์†Œ๋น„์ž์˜ ํ†ตํ•ฉ ์–ด๋ ค์›€

ํ›Œ๋ฅญํ•œ API ์„ค๊ณ„๋Š” ๋ณด์ด์ง€ ์•Š์Šต๋‹ˆ๋‹คโ€”์‚ฌ์šฉ์ž๋Š” ๋งˆ์ฐฐ ์—†์ด ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

API๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

API๋Š” ๊ตฌํ˜„ ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์ˆจ๊ธฐ๋ฉด์„œ ๊ธฐ๋Šฅ์„ ๋…ธ์ถœํ•˜๋Š” ์ถ”์ƒํ™”(abstraction)์ž…๋‹ˆ๋‹ค:

# ๋‚˜์จ: ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ ๋…ธ์ถœ
user_data = database.execute_raw_sql("SELECT * FROM users WHERE id = ?", user_id)

# ์ข‹์Œ: API๋ฅผ ํ†ตํ•œ ์ถ”์ƒํ™”
user = user_service.get_user_by_id(user_id)

API์˜ ์œ ํ˜•

  1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ/๋ชจ๋“ˆ API: ์ฝ”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ํ•จ์ˆ˜์™€ ํด๋ž˜์Šค
  2. ์›น API: HTTP ๊ธฐ๋ฐ˜ ์„œ๋น„์Šค(REST, GraphQL, gRPC)
  3. ์šด์˜์ฒด์ œ API: ์‹œ์Šคํ…œ ์ฝœ, ํŒŒ์ผ I/O, ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ
  4. ํ•˜๋“œ์›จ์–ด API: ๋””๋ฐ”์ด์Šค ๋“œ๋ผ์ด๋ฒ„, ํŽŒ์›จ์–ด ์ธํ„ฐํŽ˜์ด์Šค

์ด ๋ ˆ์Šจ์€ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์„ค๊ณ„ ๊ณผ์ œ๋ฅผ ๋Œ€ํ‘œํ•˜๋Š” ์›น API์™€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ API์— ์ฃผ๋กœ ์ดˆ์ ์„ ๋งž์ถฅ๋‹ˆ๋‹ค.

REST API ์„ค๊ณ„

REST(Representational State Transfer)๋Š” HTTP ๋ฉ”์„œ๋“œ์™€ URI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ํ‘œํ˜„ํ•˜๋Š” ์›น API์˜ ์•„ํ‚คํ…์ฒ˜ ์Šคํƒ€์ผ์ž…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ์›์น™

1. ๋ฆฌ์†Œ์Šค์™€ URI

๋ฆฌ์†Œ์Šค๋Š” ๋ช…์‚ฌ(noun)์ด์ง€, ๋™์‚ฌ(verb)๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. URI๋Š” ๋ฆฌ์†Œ์Šค๋ฅผ ์‹๋ณ„ํ•˜๊ณ , HTTP ๋ฉ”์„œ๋“œ๋Š” ์ž‘์—…์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

์ข‹์€ URI ์„ค๊ณ„:

GET    /users              # ์‚ฌ์šฉ์ž ๋ชฉ๋ก
GET    /users/123          # ์‚ฌ์šฉ์ž 123 ์กฐํšŒ
POST   /users              # ์‚ฌ์šฉ์ž ์ƒ์„ฑ
PUT    /users/123          # ์‚ฌ์šฉ์ž 123 ์—…๋ฐ์ดํŠธ
DELETE /users/123          # ์‚ฌ์šฉ์ž 123 ์‚ญ์ œ
GET    /users/123/orders   # ์‚ฌ์šฉ์ž 123์˜ ์ฃผ๋ฌธ ์กฐํšŒ

๋‚˜์œ URI ์„ค๊ณ„:

GET    /getUsers           # URI์— ๋™์‚ฌ
POST   /createUser         # URI์— ๋™์‚ฌ
GET    /users/delete/123   # URI ๊ฒฝ๋กœ์— ์•ก์…˜
GET    /user_orders?id=123 # ์ผ๊ด€์„ฑ ์—†๋Š” ๋„ค์ด๋ฐ

๋„ค์ด๋ฐ ๊ทœ์น™: - ์ปฌ๋ ‰์…˜์—๋Š” ๋ณต์ˆ˜ ๋ช…์‚ฌ ์‚ฌ์šฉ(/users, /user ์•„๋‹˜) - ์†Œ๋ฌธ์ž ์‚ฌ์šฉ, ํ•˜์ดํ”ˆ์œผ๋กœ ๊ตฌ๋ถ„(/order-items, /OrderItems ์•„๋‹˜) - ๊ฒฝ๋กœ ์„ธ๊ทธ๋จผํŠธ๋กœ ๊ณ„์ธต ๊ตฌ์กฐ ํ‘œํ˜„(/users/123/addresses/456)

2. HTTP ๋ฉ”์„œ๋“œ

HTTP ๋ฉ”์„œ๋“œ๋ฅผ ์˜๋ฏธ๋ก ์ ์œผ๋กœ ์‚ฌ์šฉ:

๋ฉ”์„œ๋“œ ๋ชฉ์  ๋ฉฑ๋“ฑ์„ฑ? ์•ˆ์ „์„ฑ?
GET ๋ฆฌ์†Œ์Šค ์กฐํšŒ Yes Yes
POST ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ No No
PUT ๋ฆฌ์†Œ์Šค ๊ต์ฒด Yes No
PATCH ๋ถ€๋ถ„ ์—…๋ฐ์ดํŠธ No No
DELETE ๋ฆฌ์†Œ์Šค ์ œ๊ฑฐ Yes No

๋ฉฑ๋“ฑ์„ฑ(Idempotent): ๋™์ผํ•œ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด๋„ ํ•œ ๋ฒˆ์˜ ์š”์ฒญ๊ณผ ๊ฐ™์€ ํšจ๊ณผ ์•ˆ์ „์„ฑ(Safe): ์š”์ฒญ์ด ์„œ๋ฒ„ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Œ

// Express.js ์˜ˆ์ œ
const express = require('express');
const app = express();

app.get('/users/:id', (req, res) => {
  const user = userService.findById(req.params.id);
  res.json(user);
});

app.post('/users', (req, res) => {
  const user = userService.create(req.body);
  res.status(201).json(user);
});

app.put('/users/:id', (req, res) => {
  const user = userService.replace(req.params.id, req.body);
  res.json(user);
});

app.patch('/users/:id', (req, res) => {
  const user = userService.update(req.params.id, req.body);
  res.json(user);
});

app.delete('/users/:id', (req, res) => {
  userService.delete(req.params.id);
  res.status(204).send();
});

3. HTTP ์ƒํƒœ ์ฝ”๋“œ

๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด ์ ์ ˆํ•œ ์ƒํƒœ ์ฝ”๋“œ ์‚ฌ์šฉ:

2xx ์„ฑ๊ณต: - 200 OK: ํ‘œ์ค€ ์„ฑ๊ณต ์‘๋‹ต - 201 Created: ๋ฆฌ์†Œ์Šค ์ƒ์„ฑ๋จ(POST) - 204 No Content: ์‘๋‹ต ๋ณธ๋ฌธ ์—†์ด ์„ฑ๊ณต(DELETE)

4xx ํด๋ผ์ด์–ธํŠธ ์˜ค๋ฅ˜: - 400 Bad Request: ์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ - 401 Unauthorized: ์ธ์ฆ ํ•„์š” - 403 Forbidden: ์ธ์ฆ๋˜์—ˆ์ง€๋งŒ ๊ถŒํ•œ ์—†์Œ - 404 Not Found: ๋ฆฌ์†Œ์Šค๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์Œ - 409 Conflict: ๋ฆฌ์†Œ์Šค ์ƒํƒœ ์ถฉ๋Œ - 422 Unprocessable Entity: ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์˜ค๋ฅ˜

5xx ์„œ๋ฒ„ ์˜ค๋ฅ˜: - 500 Internal Server Error: ์ผ๋ฐ˜ ์„œ๋ฒ„ ์˜ค๋ฅ˜ - 503 Service Unavailable: ์ผ์‹œ์  ์ค‘๋‹จ

# Flask ์˜ˆ์ œ
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.route('/users', methods=['POST'])
def create_user():
    data = request.get_json()

    if not data or 'email' not in data:
        return jsonify({'error': 'Email is required'}), 400

    if user_exists(data['email']):
        return jsonify({'error': 'User already exists'}), 409

    user = create_user_in_db(data)
    return jsonify(user), 201

@app.route('/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = find_user(user_id)

    if not user:
        return jsonify({'error': 'User not found'}), 404

    return jsonify(user), 200

4. ํŽ˜์ด์ง€๋„ค์ด์…˜

์ปฌ๋ ‰์…˜์˜ ๊ฒฝ์šฐ, ์••๋„์ ์ธ ์‘๋‹ต์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„:

์˜คํ”„์…‹ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜:

GET /users?offset=20&limit=10

Response:
{
  "data": [...],
  "pagination": {
    "offset": 20,
    "limit": 10,
    "total": 150
  }
}

์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜(์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ์— ๋” ์ ํ•ฉ):

GET /users?cursor=eyJpZCI6MTIzfQ==&limit=10

Response:
{
  "data": [...],
  "next_cursor": "eyJpZCI6MTMzfQ=="
}

5. ํ•„ํ„ฐ๋ง, ์ •๋ ฌ, ๊ฒ€์ƒ‰

์œ ์—ฐํ•œ ๋ฐ์ดํ„ฐ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ œ๊ณต:

GET /users?role=admin&sort=-created_at&search=john
// Java Spring Boot ์˜ˆ์ œ
@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping
    public ResponseEntity<Page<User>> getUsers(
            @RequestParam(required = false) String role,
            @RequestParam(required = false) String search,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "20") int size,
            @RequestParam(defaultValue = "id") String sortBy,
            @RequestParam(defaultValue = "ASC") Sort.Direction direction
    ) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
        Page<User> users = userService.findUsers(role, search, pageable);
        return ResponseEntity.ok(users);
    }
}

6. HATEOAS

HATEOAS(Hypermedia As The Engine Of Application State)๋Š” ์‘๋‹ต์— ๊ด€๋ จ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ๋งํฌ๋ฅผ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค:

{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "_links": {
    "self": { "href": "/users/123" },
    "orders": { "href": "/users/123/orders" },
    "addresses": { "href": "/users/123/addresses" }
  }
}

์ด๋Š” API๋ฅผ ์ž์ฒด ๋ฌธ์„œํ™”ํ•˜๊ณ  ๋ฐœ๊ฒฌ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

RPC ์Šคํƒ€์ผ API

RPC(Remote Procedure Call) API๋Š” ๋ฆฌ์†Œ์Šค๊ฐ€ ์•„๋‹Œ ํ•จ์ˆ˜ ํ˜ธ์ถœ๋กœ ์ž‘์—…์„ ๋ชจ๋ธ๋งํ•ฉ๋‹ˆ๋‹ค.

gRPC

gRPC๋Š” ํšจ์œจ์ ์ธ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ง๋ ฌํ™”๋ฅผ ์œ„ํ•ด ํ”„๋กœํ† ์ฝœ ๋ฒ„ํผ(Protocol Buffers)๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

// user.proto
syntax = "proto3";

service UserService {
  rpc GetUser (GetUserRequest) returns (User);
  rpc CreateUser (CreateUserRequest) returns (User);
  rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
}

message User {
  int32 id = 1;
  string name = 2;
  string email = 3;
}

message GetUserRequest {
  int32 id = 1;
}
# Python gRPC ์„œ๋ฒ„
import grpc
from concurrent import futures
import user_pb2
import user_pb2_grpc

class UserService(user_pb2_grpc.UserServiceServicer):
    def GetUser(self, request, context):
        user = find_user(request.id)
        if not user:
            context.abort(grpc.StatusCode.NOT_FOUND, 'User not found')
        return user_pb2.User(id=user.id, name=user.name, email=user.email)

server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
user_pb2_grpc.add_UserServiceServicer_to_server(UserService(), server)
server.add_insecure_port('[::]:50051')
server.start()

RPC vs REST ์–ธ์ œ ์‚ฌ์šฉํ• ๊นŒ

RPC(gRPC)๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ: - ๊ณ ์„ฑ๋Šฅ๊ณผ ๋‚ฎ์€ ์ง€์—ฐ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ - ์–‘๋ฐฉํ–ฅ ์ŠคํŠธ๋ฆฌ๋ฐ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ - ๋‚ด๋ถ€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํ†ต์‹  - ํƒ€์ž… ์•ˆ์ „์„ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ

REST๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ: - ๊ณต๊ฐœ API - ๊ด‘๋ฒ”์œ„ํ•œ ํด๋ผ์ด์–ธํŠธ ํ˜ธํ™˜์„ฑ(๋ธŒ๋ผ์šฐ์ €, ๋ชจ๋ฐ”์ผ) - ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๋””๋ฒ„๊น… - ์บ์‹ฑ๊ณผ ํ‘œ์ค€ HTTP ํˆด๋ง

GraphQL

GraphQL์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •ํ™•ํžˆ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ์ฟผ๋ฆฌ ์–ธ์–ด์ž…๋‹ˆ๋‹ค.

์Šคํ‚ค๋งˆ ์ •์˜

type User {
  id: ID!
  name: String!
  email: String!
  orders: [Order!]!
}

type Order {
  id: ID!
  total: Float!
  items: [OrderItem!]!
}

type Query {
  user(id: ID!): User
  users(limit: Int, offset: Int): [User!]!
}

type Mutation {
  createUser(name: String!, email: String!): User!
  updateUser(id: ID!, name: String, email: String): User!
}

์ฟผ๋ฆฌ์™€ ๋ฎคํ…Œ์ด์…˜

// ํด๋ผ์ด์–ธํŠธ ์ฟผ๋ฆฌ - ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์š”์ฒญ
const query = `
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      orders {
        total
      }
    }
  }
`;

// ์„œ๋ฒ„ ๋ฆฌ์กธ๋ฒ„(Node.js)
const resolvers = {
  Query: {
    user: (parent, { id }, context) => {
      return context.db.findUserById(id);
    },
  },
  User: {
    orders: (user, args, context) => {
      return context.db.findOrdersByUserId(user.id);
    },
  },
  Mutation: {
    createUser: (parent, { name, email }, context) => {
      return context.db.createUser({ name, email });
    },
  },
};

N+1 ๋ฌธ์ œ

GraphQL์€ ์ค‘์ฒฉ ํ•„๋“œ๋ฅผ ํ•ด๊ฒฐํ•  ๋•Œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

query {
  users {
    name
    orders {  # N+1: ์‚ฌ์šฉ์ž๋‹น ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ
      total
    }
  }
}

ํ•ด๊ฒฐ์ฑ…: DataLoader๋Š” ์š”์ฒญ์„ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌํ•˜๊ณ  ์บ์‹œํ•ฉ๋‹ˆ๋‹ค:

const DataLoader = require('dataloader');

const orderLoader = new DataLoader(async (userIds) => {
  const orders = await db.findOrdersByUserIds(userIds);
  return userIds.map(id => orders.filter(o => o.userId === id));
});

const resolvers = {
  User: {
    orders: (user, args, { orderLoader }) => {
      return orderLoader.load(user.id);
    },
  },
};

GraphQL์ด ๋น›๋‚˜๋Š” ๊ฒฝ์šฐ

GraphQL์„ ์‚ฌ์šฉํ•  ๋•Œ: - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์œ ์—ฐํ•œ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ๋ฅผ ํ•„์š”๋กœ ํ•  ๋•Œ(๋ชจ๋ฐ”์ผ, ์›น ๋Œ€์‹œ๋ณด๋“œ) - ๊ณผ๋‹ค ๊ฐ€์ ธ์˜ค๊ธฐ(over-fetching)๋‚˜ ๋ถ€์กฑ ๊ฐ€์ ธ์˜ค๊ธฐ(under-fetching)๋ฅผ ํ”ผํ•˜๊ณ  ์‹ถ์„ ๋•Œ - ๋ฐฑ์—”๋“œ ๋ณ€๊ฒฝ ์—†์ด ๋น ๋ฅธ ํ”„๋ก ํŠธ์—”๋“œ ๋ฐ˜๋ณต

GraphQL์„ ํ”ผํ•ด์•ผ ํ•  ๋•Œ: - ๊ฐ„๋‹จํ•œ CRUD ์ž‘์—…(REST๊ฐ€ ๋” ๊ฐ„๋‹จ) - ํŒŒ์ผ ์—…๋กœ๋“œ(GraphQL์€ ํŒŒ์ผ ์ฒ˜๋ฆฌ๊ฐ€ ์ทจ์•ฝ) - ์บ์‹ฑ์ด ์ค‘์š”ํ•œ ๊ฒฝ์šฐ(HTTP ์บ์‹ฑ์ด REST์—์„œ ๋” ์ž˜ ์ž‘๋™)

API ์„ค๊ณ„ ์›์น™

1. ์ผ๊ด€์„ฑ

์ผ๊ด€์„ฑ์€ ์ธ์ง€ ๋ถ€ํ•˜๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค. ๊ทœ์น™์„ ์ •ํ•˜๊ณ  ๋”ฐ๋ฅด์„ธ์š”:

๋„ค์ด๋ฐ ๊ทœ์น™:

# ์ผ๊ด€์„ฑ ์žˆ์Œ
GET /users
GET /orders
GET /products

# ์ผ๊ด€์„ฑ ์—†์Œ
GET /users
GET /getOrders
GET /product_list

์‘๋‹ต ํ˜•์‹:

// ์ผ๊ด€๋œ ์„ฑ๊ณต ์‘๋‹ต
{
  "data": { ... },
  "meta": { ... }
}

// ์ผ๊ด€๋œ ์˜ค๋ฅ˜ ์‘๋‹ต
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Email is required",
    "fields": { "email": "This field is required" }
  }
}

2. ์ตœ์†Œ ๋†€๋žŒ์˜ ์›์น™

API๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์˜ˆ์ƒํ•˜๋Š” ๋Œ€๋กœ ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋†€๋ผ์šด ๋™์ž‘์„ ํ”ผํ•˜์„ธ์š”:

# ๋†€๋ผ์›€: ๋ฉ”์„œ๋“œ ์ด๋ฆ„์ด ์ฝ๊ธฐ ์ „์šฉ์„ ์•”์‹œํ•˜์ง€๋งŒ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝ
def get_user_or_create(email):
    user = find_user(email)
    if not user:
        user = create_user(email)  # ๋†€๋žŒ!
    return user

# ๋ช…ํ™•ํ•จ: ์ด๋ฆ„์ด ๊ฐ€๋Šฅํ•œ ๋ณ€๊ฒฝ์„ ๋‚˜ํƒ€๋ƒ„
def find_or_create_user(email):
    user = find_user(email)
    if not user:
        user = create_user(email)
    return user

3. ๋ฉฑ๋“ฑ์„ฑ

๋ฉฑ๋“ฑ์„ฑ ์ž‘์—…์€ ๋ถ€์ž‘์šฉ ์—†์ด ์•ˆ์ „ํ•˜๊ฒŒ ์žฌ์‹œ๋„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

# ๋ฉฑ๋“ฑ์„ฑ: PUT์€ ์ „์ฒด ๋ฆฌ์†Œ์Šค๋ฅผ ๊ต์ฒด
PUT /users/123
{ "name": "John", "email": "john@example.com" }

# ๋น„๋ฉฑ๋“ฑ์„ฑ: PATCH๋Š” ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ์Œ
PATCH /users/123
{ "login_count": 5 }  # ์žฌ์‹œ๋„ํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

# ๋” ๋‚˜์Œ: ์„œ๋ฒ„ ์ œ์–ด ์ฆ๊ฐ€
POST /users/123/login

4. ํ•˜์œ„ ํ˜ธํ™˜์„ฑ

๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ ˆ๋Œ€ ๊นจ๋œจ๋ฆฌ์ง€ ๋งˆ์„ธ์š”. API๋ฅผ ์‹ ์ค‘ํ•˜๊ฒŒ ๋ฐœ์ „์‹œํ‚ค์„ธ์š”:

์•ˆ์ „ํ•œ ๋ณ€๊ฒฝ: - ์„ ํƒ์  ํ•„๋“œ ์ถ”๊ฐ€ - ์ƒˆ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ - ํ•„๋“œ ํ๊ธฐ(์œ ์˜ˆ ๊ธฐ๊ฐ„ ํฌํ•จ)

ํŒŒ๊ดด์  ๋ณ€๊ฒฝ: - ํ•„๋“œ ์ œ๊ฑฐ - ํ•„๋“œ ํƒ€์ž… ๋ณ€๊ฒฝ - ํ•„๋“œ ์ด๋ฆ„ ๋ณ€๊ฒฝ - ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ทœ์น™ ๋ณ€๊ฒฝ

// ํ•˜์œ„ ํ˜ธํ™˜: ์ƒˆ๋กœ์šด ์„ ํƒ์  ํ•„๋“œ
{
  "name": "John",
  "email": "john@example.com",
  "phone": "555-1234"  // ์ƒˆ๋กœ์šด, ์„ ํƒ์ 
}

// ํŒŒ๊ดด์ : ํ•„๋“œ ์ œ๊ฑฐ
{
  "name": "John"
  // "email" ์ œ๊ฑฐ - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊นจ์ง!
}

๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต

ํŒŒ๊ดด์  ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•  ๋•Œ, API๋ฅผ ๋ฒ„์ „ ๊ด€๋ฆฌํ•˜์„ธ์š”:

1. URL ๋ฒ„์ „ ๊ด€๋ฆฌ

GET /v1/users
GET /v2/users

์žฅ์ : ๋ช…ํ™•ํ•˜๊ณ , ๋ผ์šฐํŒ…ํ•˜๊ธฐ ์‰ฌ์›€ ๋‹จ์ : URL ๋ณ€๊ฒฝ, ์บ์‹ฑ ๋ฌธ์ œ

2. ํ—ค๋” ๋ฒ„์ „ ๊ด€๋ฆฌ

GET /users
Accept: application/vnd.myapi.v2+json

์žฅ์ : ๊นจ๋—ํ•œ URL ๋‹จ์ : ๊ฐ€์‹œ์„ฑ ๋‚ฎ์Œ, ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์–ด๋ ค์›€

3. ์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜

GET /users?version=2

์žฅ์ : ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฌ์›€ ๋‹จ์ : ๋ฒ„์ „ ๊ด€๋ฆฌ์™€ ํ•„ํ„ฐ๋ง์ด ํ˜ผ์žฌ

๊ถŒ์žฅ์‚ฌํ•ญ: ์ฃผ์š” ๋ฒ„์ „์—๋Š” URL ๋ฒ„์ „ ๊ด€๋ฆฌ, ์‚ฌ์†Œํ•œ ๋ณ€๊ฒฝ์—๋Š” ๊ธฐ๋Šฅ ํ”Œ๋ž˜๊ทธ ์‚ฌ์šฉ.

์ธ์ฆ๊ณผ ๊ถŒํ•œ ๋ถ€์—ฌ

API ํ‚ค

GET /users
X-API-Key: abc123xyz

๊ฐ„๋‹จํ•˜์ง€๋งŒ ๋ณด์•ˆ์ด ์•ฝํ•จ. ๋‚ฎ์€ ์œ„ํ—˜๋„์˜ ๋‚ด๋ถ€ API์— ์‚ฌ์šฉ.

OAuth 2.0

GET /users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

์œ„์ž„๋œ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์œ„ํ•œ ์—…๊ณ„ ํ‘œ์ค€. ์„œ๋“œํŒŒํ‹ฐ ํ†ตํ•ฉ์— ์‚ฌ์šฉ.

JWT(JSON Web Tokens)

// Node.js JWT ์˜ˆ์ œ
const jwt = require('jsonwebtoken');

// ํ† ํฐ ์ƒ์„ฑ
const token = jwt.sign(
  { userId: 123, role: 'admin' },
  process.env.SECRET_KEY,
  { expiresIn: '1h' }
);

// ํ† ํฐ ๊ฒ€์ฆ
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).send('Unauthorized');

  try {
    const payload = jwt.verify(token, process.env.SECRET_KEY);
    req.user = payload;
    next();
  } catch (err) {
    res.status(403).send('Invalid token');
  }
}

์†๋„ ์ œํ•œ๊ณผ ์Šค๋กœํ‹€๋ง

API๋ฅผ ๋‚จ์šฉ์œผ๋กœ๋ถ€ํ„ฐ ๋ณดํ˜ธํ•˜์„ธ์š”:

# Python Flask ์†๋„ ์ œํ•œ
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["100 per hour"]
)

@app.route('/api/search')
@limiter.limit("10 per minute")
def search():
    return perform_expensive_search()

์‘๋‹ต ํ—ค๋”:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1614556800

์ œํ•œ ์ดˆ๊ณผ ์‹œ 429 Too Many Requests ๋ฐ˜ํ™˜.

์˜ค๋ฅ˜ ์‘๋‹ต

์ผ๊ด€๋˜๊ณ  ์œ ์šฉํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€ ์ œ๊ณต:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      },
      {
        "field": "age",
        "message": "Age must be at least 18"
      }
    ],
    "request_id": "req_abc123"
  }
}
// C++ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ API ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
class ApiException : public std::exception {
private:
    std::string message_;
    int code_;
public:
    ApiException(int code, const std::string& msg)
        : code_(code), message_(msg) {}

    int code() const { return code_; }
    const char* what() const noexcept override {
        return message_.c_str();
    }
};

class UserService {
public:
    User getUser(int id) {
        if (id < 0) {
            throw ApiException(400, "Invalid user ID");
        }
        auto user = db.findById(id);
        if (!user) {
            throw ApiException(404, "User not found");
        }
        return user;
    }
};

๋ฌธ์„œํ™”

ํ›Œ๋ฅญํ•œ API๋Š” ํ›Œ๋ฅญํ•œ ๋ฌธ์„œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

OpenAPI(Swagger)

# openapi.yaml
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users/{userId}:
    get:
      summary: Get user by ID
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: integer
      responses:
        '200':
          description: User found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '404':
          description: User not found
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email

์žฅ์ : - ์ž๋™ ์ƒ์„ฑ๋œ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๋ฌธ์„œ - ํด๋ผ์ด์–ธํŠธ SDK ์ƒ์„ฑ - ์œ ํšจ์„ฑ ๊ฒ€์ฆ

ํ•„์ˆ˜ ๋ฌธ์„œ ์š”์†Œ

  1. ์‹œ์ž‘ํ•˜๊ธฐ: ์ฒซ API ํ˜ธ์ถœ์„ ์œ„ํ•œ ๋น ๋ฅธ ์˜ˆ์ œ
  2. ์ธ์ฆ: ์ž๊ฒฉ ์ฆ๋ช…์„ ์–ป๊ณ  ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•
  3. ์†๋„ ์ œํ•œ: ์Šค๋กœํ‹€๋ง ๊ทœ์น™๊ณผ ํ—ค๋”
  4. ์˜ค๋ฅ˜ ์ฝ”๋“œ: ์˜ˆ์ œ๊ฐ€ ํฌํ•จ๋œ ํฌ๊ด„์  ๋ชฉ๋ก
  5. SDK: ์ธ๊ธฐ ์žˆ๋Š” ์–ธ์–ด์šฉ ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  6. ๋ณ€๊ฒฝ ๋กœ๊ทธ: ๋ฒ„์ „ ํžˆ์Šคํ† ๋ฆฌ์™€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฐ€์ด๋“œ

๋‚ด๋ถ€ API ์„ค๊ณ„(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋ชจ๋“ˆ)

์ตœ์†Œ ํ‘œ๋ฉด์ 

ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ๋…ธ์ถœ:

// ๋‚˜์จ: ๋‚ด๋ถ€ ์‚ฌํ•ญ ๋…ธ์ถœ
public class UserService {
    public Database db;  // ๋…ธ์ถœ๋จ!
    public void validateEmail(String email) { ... }  // ๊ณต๊ฐœ์ด์ง€๋งŒ ๋‚ด๋ถ€์šฉ
}

// ์ข‹์Œ: ์ตœ์†Œ ์ธํ„ฐํŽ˜์ด์Šค
public class UserService {
    private Database db;

    public User getUser(int id) { ... }
    public User createUser(UserData data) { ... }

    private void validateEmail(String email) { ... }  // ๋น„๊ณต๊ฐœ
}

์บก์Аํ™”

๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ ์ˆจ๊ธฐ๊ธฐ:

# ๋‚˜์จ: ๊ตฌํ˜„ ๋…ธ์ถœ
class ShoppingCart:
    def __init__(self):
        self.items = []  # ๋‚ด๋ถ€ ๋ฆฌ์ŠคํŠธ์— ์ง์ ‘ ์ ‘๊ทผ

    def add_item(self, item):
        self.items.append(item)

cart = ShoppingCart()
cart.items.append(invalid_item)  # ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์šฐํšŒ ๊ฐ€๋Šฅ!

# ์ข‹์Œ: ์บก์Аํ™”๋จ
class ShoppingCart:
    def __init__(self):
        self._items = []

    def add_item(self, item):
        self._validate_item(item)
        self._items.append(item)

    def get_items(self):
        return self._items.copy()  # ์ฐธ์กฐ๊ฐ€ ์•„๋‹Œ ๋ณต์‚ฌ๋ณธ ๋ฐ˜ํ™˜

์œ ์ฐฝํ•œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ๋นŒ๋” ํŒจํ„ด

์‚ฌ์šฉํ•˜๊ธฐ ์ฆ๊ฑฐ์šด API ๋งŒ๋“ค๊ธฐ:

// ์œ ์ฐฝํ•œ ์ธํ„ฐํŽ˜์ด์Šค
const query = new QueryBuilder()
  .select('name', 'email')
  .from('users')
  .where('age', '>', 18)
  .orderBy('name')
  .limit(10)
  .build();

// ๋นŒ๋” ํŒจํ„ด
const user = new UserBuilder()
  .withName('John Doe')
  .withEmail('john@example.com')
  .withRole('admin')
  .build();

์ผ๊ด€๋œ ๋„ค์ด๋ฐ๊ณผ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ˆœ์„œ

# ์ผ๊ด€์„ฑ: ๋ฆฌ์†Œ์Šค ๋จผ์ €, ๊ทธ ๋‹ค์Œ ์˜ต์…˜
user_service.get_user(user_id, include_deleted=False)
order_service.get_order(order_id, include_items=True)

# ์ผ๊ด€์„ฑ ์—†์Œ
user_service.get_user(include_deleted=False, user_id=123)
order_service.find_order(123, True)  # True๊ฐ€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”๊ฐ€?

์—ฐ์Šต ๋ฌธ์ œ

์—ฐ์Šต ๋ฌธ์ œ 1: ์„œ์ ์„ ์œ„ํ•œ REST API ์„ค๊ณ„

๋‹ค์Œ ์š”๊ตฌ์‚ฌํ•ญ์„ ๊ฐ€์ง„ ์˜จ๋ผ์ธ ์„œ์ ์„ ์œ„ํ•œ REST API๋ฅผ ์„ค๊ณ„ํ•˜์„ธ์š”: - ์ฑ… ํƒ์ƒ‰(์žฅ๋ฅด, ์ €์ž๋ณ„ ํ•„ํ„ฐ๋ง) - ์ฑ… ๊ฒ€์ƒ‰ - ์‚ฌ์šฉ์ž ๊ณ„์ •๊ณผ ์ธ์ฆ - ์žฅ๋ฐ”๊ตฌ๋‹ˆ - ์ฃผ๋ฌธ ๋ฐฐ์น˜ - ์ฃผ๋ฌธ ๋‚ด์—ญ

์ œ๊ณตํ•  ๊ฒƒ: 1. HTTP ๋ฉ”์„œ๋“œ๊ฐ€ ํฌํ•จ๋œ ์—”๋“œํฌ์ธํŠธ ๋ชฉ๋ก 2. ์ƒ˜ํ”Œ ์š”์ฒญ/์‘๋‹ต ๋ณธ๋ฌธ 3. ๊ฐ ์—”๋“œํฌ์ธํŠธ์˜ ์ƒํƒœ ์ฝ”๋“œ 4. ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ „๋žต

์—ฐ์Šต ๋ฌธ์ œ 2: API ๊ฒ€ํ† 

์ด API ์„ค๊ณ„๋ฅผ ๊ฒ€ํ† ํ•˜๊ณ  ์ข‹์€ API ์„ค๊ณ„ ์›์น™์˜ ์œ„๋ฐ˜ ์‚ฌํ•ญ์„ ์‹๋ณ„ํ•˜์„ธ์š”:

GET /getBook?id=123
POST /createBook?title=NewBook&author=JohnDoe
GET /updateBook?id=123&title=UpdatedTitle
DELETE /books/remove/123
GET /books?page=1&limit=1000
POST /users/login  (์ž๊ฒฉ ์ฆ๋ช…์ด ํ‹€๋ ค๋„ 200 ๋ฐ˜ํ™˜)

๋ฌด์—‡์ด ์ž˜๋ชป๋˜์—ˆ๋Š”์ง€ ๋‚˜์—ดํ•˜๊ณ  ๊ฐœ์„ ์•ˆ์„ ์ œ์•ˆํ•˜์„ธ์š”.

์—ฐ์Šต ๋ฌธ์ œ 3: ๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต

ํ˜„์žฌ API์— ๋‹ค์Œ ์—”๋“œํฌ์ธํŠธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

GET /users/123
Response: { "name": "John", "email": "john@example.com" }

ํŒŒ๊ดด์  ๋ณ€๊ฒฝ์„ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: name์„ first_name๊ณผ last_name์œผ๋กœ ๋ถ„ํ• . ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต์„ ์„ค๊ณ„ํ•˜์„ธ์š”: 1. ๊ธฐ์กด ํด๋ผ์ด์–ธํŠธ๋ฅผ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š์Œ 2. ์ƒˆ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐœ์„ ๋œ ๊ตฌ์กฐ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ 3. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฝ๋กœ ์ œ๊ณต

์—ฐ์Šต ๋ฌธ์ œ 4: ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์„ค๊ณ„

๋‹ค์Œ์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ๋ฑ…ํ‚น API๋ฅผ ์œ„ํ•œ ํฌ๊ด„์ ์ธ ์˜ค๋ฅ˜ ์‘๋‹ต ํ˜•์‹์„ ์„ค๊ณ„ํ•˜์„ธ์š”: - ์œ ํšจ์„ฑ ๊ฒ€์ฆ ์˜ค๋ฅ˜(์—ฌ๋Ÿฌ ํ•„๋“œ) - ์ž”์•ก ๋ถ€์กฑ - ๊ณ„์ • ์ž ๊น€ - ์†๋„ ์ œํ•œ - ์„œ๋ฒ„ ์˜ค๋ฅ˜

๊ฐ ์˜ค๋ฅ˜ ์œ ํ˜•์— ๋Œ€ํ•œ JSON ์˜ˆ์ œ๋ฅผ ์ œ๊ณตํ•˜์„ธ์š”.

์—ฐ์Šต ๋ฌธ์ œ 5: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ API ์„ค๊ณ„

๋‹ค์Œ์„ ์ง€์›ํ•˜๋Š” ์„ ํ˜ธํ•˜๋Š” ์–ธ์–ด๋กœ SQL ์ฟผ๋ฆฌ ๋นŒ๋”๋ฅผ ์œ„ํ•œ ์œ ์ฐฝํ•œ API๋ฅผ ์„ค๊ณ„ํ•˜์„ธ์š”: - ์—ฌ๋Ÿฌ ์ปฌ๋Ÿผ์œผ๋กœ SELECT - ํ…Œ์ด๋ธ” ์ด๋ฆ„์œผ๋กœ FROM - ์—ฌ๋Ÿฌ ์กฐ๊ฑด์œผ๋กœ WHERE(AND/OR) - JOIN ์ž‘์—… - ORDER BY - LIMIT๊ณผ OFFSET

์‚ฌ์šฉ ์˜ˆ์ œ:

query.select('id', 'name')
     .from('users')
     .where('age > ?', 18)
     .orderBy('name')
     .limit(10)

์š”์•ฝ

์ข‹์€ API ์„ค๊ณ„๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ๊ณต๊ฐ์ž…๋‹ˆ๋‹ค:

  • REST: ๋ฆฌ์†Œ์Šค ์ง€ํ–ฅ, HTTP ์˜๋ฏธ๋ก  ์‚ฌ์šฉ, ๊ณต๊ฐœ API์— ์ตœ์ 
  • RPC/gRPC: ์•ก์…˜ ์ง€ํ–ฅ, ํšจ์œจ์ , ๋‚ด๋ถ€ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— ์ตœ์ 
  • GraphQL: ์œ ์—ฐํ•œ ์ฟผ๋ฆฌ, ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ตœ์ 
  • ์ผ๊ด€์„ฑ: ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋„ค์ด๋ฐ, ๊ตฌ์กฐ, ๋™์ž‘
  • ๋ฉฑ๋“ฑ์„ฑ: PUT๊ณผ DELETE์˜ ์•ˆ์ „ํ•œ ์žฌ์‹œ๋„
  • ๋ฒ„์ „ ๊ด€๋ฆฌ: ํด๋ผ์ด์–ธํŠธ๋ฅผ ๊นจ๋œจ๋ฆฌ์ง€ ์•Š๊ณ  ๋ณ€๊ฒฝ ๊ด€๋ฆฌ
  • ๋ฌธ์„œํ™”: ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด API๋ฅผ ์ž๋ช…ํ•˜๊ฒŒ ๋งŒ๋“ค๊ธฐ
  • ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ: ๋ช…ํ™•ํ•˜๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

๊ธฐ์–ตํ•˜์„ธ์š”: API๋Š” ์ œํ’ˆ์ž…๋‹ˆ๋‹ค. API ์†Œ๋น„์ž๋ฅผ ๊ณ ๊ฐ์œผ๋กœ ๋Œ€ํ•˜์„ธ์š”โ€”๊ทธ๋“ค์˜ ๊ฒฝํ—˜์— ํˆฌ์žํ•˜์„ธ์š”.

๋‚ด๋น„๊ฒŒ์ด์…˜

โ† ์ด์ „: ์„ฑ๋Šฅ ์ตœ์ ํ™” | ๋‹ค์Œ: ๋ฒ„์ „ ๊ด€๋ฆฌ ์›Œํฌํ”Œ๋กœ์šฐ โ†’

to navigate between lessons