CRUD는 생성, 읽기, 갱신, 삭제의 약자로, 모든 영속 데이터 저장소가 지원해야 하는 네 가지 기본 연산입니다. James Martin의 1983년 저서 Managing the Data-base Environment에서 처음 쓰인 이 약어는 백엔드 분야에서 가장 오래 살아남은 용어입니다. 최소한의 바닥을 가리키기 때문입니다. 시스템이 그 밖에 무엇을 하든, 레코드는 생성되고, 조회되고, 변경되고, 삭제될 수 있어야 합니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 네 가지 동사 | 생성 · 읽기 · 갱신 · 삭제. 영속성의 최소 요건 |
| SQL에서 | INSERT · SELECT · UPDATE · DELETE |
| HTTP에서 | POST · GET · PUT/PATCH · DELETE |
| vs. REST | CRUD는 데이터에 하는 일, REST는 클라이언트가 리소스에 도달하는 방식 |
| 현대적인 접근 | CRUD 계층은 직접 작성하는 것이 아니라 생성되는 것 |
네 가지 언어로 보는 CRUD
-- SQL로 표현한 CRUD: 원래의 매핑
INSERT INTO tasks (title) VALUES ('Write the launch post'); -- C
SELECT * FROM tasks WHERE done = false; -- R
UPDATE tasks SET done = true WHERE id = 42; -- U
DELETE FROM tasks WHERE id = 42; -- D
같은 주기를 SDK로 표현하면 다음과 같습니다. 대부분의 애플리케이션 코드가 실제로 작성하는 형태입니다:
// JavaScript / Node.js — Back4app JS SDK
// The full CRUD cycle on one object
const task = new Parse.Object('Task');
task.set('title', 'Write the launch post'); // C — create
await task.save();
const fetched = await new Parse.Query('Task')
.equalTo('done', false).first(); // R — read
fetched.set('done', true); // U — update
await fetched.save();
await fetched.destroy(); // D — delete // Flutter / Dart — Back4app Flutter SDK
// The full CRUD cycle on one object
final task = ParseObject('Task')..set('title', 'Write the launch post');
await task.save(); // C — create
final query = QueryBuilder<ParseObject>(ParseObject('Task'))
..whereEqualTo('done', false);
final fetched = (await query.query()).results!.first; // R — read
fetched.set('done', true);
await fetched.save(); // U — update
await fetched.delete(); // D — delete // iOS / Swift — Back4app Swift SDK
// The full CRUD cycle on one object
var task = Task()
task.title = "Write the launch post"
let saved = try await task.save() // C — create
let fetched = try await Task.query("done" == false)
.first() // R — read
var updated = fetched
updated.done = true
_ = try await updated.save() // U — update
try await updated.delete() // D — delete // Android / Kotlin — Back4app Android SDK
// The full CRUD cycle on one object
val task = ParseObject("Task").apply {
put("title", "Write the launch post")
}
task.save() // C — create
val fetched = ParseQuery.getQuery<ParseObject>("Task")
.whereEqualTo("done", false).first // R — read
fetched.put("done", true)
fetched.save() // U — update
fetched.delete() // D — delete 하나의 표로 보는 전체 매핑
어느 한 참고 자료에도 모여 있지 않은 여섯 열짜리 매핑입니다:
| 연산 | SQL | HTTP 동사 | 상태 코드 | 문서형 DB | SDK/ORM 관용구 |
|---|---|---|---|---|---|
| 생성 | INSERT | POST | 201 | insertOne | object.save() (신규) |
| 읽기 | SELECT | GET | 200 | find / findOne | query.find() / .get() |
| 갱신 | UPDATE | PUT / PATCH | 200 | updateOne | object.save() (변경된 필드) |
| 삭제 | DELETE | DELETE | 204 | deleteOne | object.destroy() |
HTTP 명세에서 실제로 알아 둘 가치가 있는 각주가 두 가지 있습니다. GET은 안전하고(상태 변경 없음), PUT과 DELETE는 멱등하며(반복해도 해가 없음), POST는 둘 다 아닙니다. 재시도 로직이 이들을 다르게 취급하는 이유이자, PUT은 “전체 교체”를, PATCH는 “일부 수정”을 뜻하는 이유입니다.
CRUD가 일어나는 곳
이 다이어그램은 보안에 대한 경고이기도 합니다. 모든 동사는 API 계층을 거치며, 권한과 검증이 있어야 할 곳이 바로 그 계층입니다. 연산별 접근 제어가 없는 CRUD 표면은 몇 단계를 더 거칠 뿐인 공개 데이터베이스입니다.
CRUD vs. REST
| 질문 | CRUD | REST |
|---|---|---|
| 가리키는 대상 | 데이터에 대한 연산 | 클라이언트와 리소스를 위한 아키텍처 스타일 |
| 정의 기준 | 네 가지 동사 | 제약 조건: 무상태, 균일한 인터페이스, 캐시 가능… |
| 존재하는 곳 | SQL, SDK, 큐 등 어디든 | HTTP API |
| 관계 | REST 엔드포인트가 흔히 다루는 페이로드 | 흔히 CRUD에 대응하지만, CRUD가 아닌 동작도 노출 가능 |
실무적인 결론은 이렇습니다. REST API는 HTTP를 입은 CRUD API인 경우가 많습니다. 하지만 “인보이스 승인”은 API에 있어야 하면서도 CRUD 동사가 아니며, CRUD는 HTTP가 전혀 없는 곳에서도 문제없이 쓰입니다. 두 용어는 경쟁하지 않고 협력합니다.
일반적인 사용 사례
- 본래 의미의 CRUD 앱. 관리자 패널, CMS, CRM, 재고, 예약 시스템입니다. 비즈니스 소프트웨어의 대다수를 차지하며, 부끄러울 것이 없습니다.
- 프로토타이핑과 MVP. 몇 개의 클래스에 적용한 네 가지 동사가 곧 대부분 제품의 첫 버전입니다.
- 자동 생성 API. 스키마를 넣으면 CRUD 표면이 나옵니다. 네 가지 동사를 코드가 아닌 설정으로 만드는 현대의 기본값이며, 자동 생성 API 항목에서 자세히 다룹니다.
- 관리 및 지원 도구. 같은 CRUD 표면 위에 얹은 시각적 그리드로, 권한도 그대로 적용됩니다.
- 모든 것의 밑바탕. 이벤트 소싱 기반이나 워크플로 중심의 시스템도 설정, 프로필, 참조 데이터처럼 어딘가에서는 여전히 CRUD를 노출합니다.
모든 동작을 CRUD로 모델링해야 할까? 결정 매트릭스
| CRUD로 모델링해야 하는 경우… | 그 이상을 선택해야 하는 경우… |
|---|---|
| 레코드의 현재 상태가 곧 진실 | 이력 자체가 도메인 → 이벤트 소싱 |
| 편집이 곧 사용자의 멘탈 모델 | 읽기와 쓰기의 서로 다른 확장 양상 → CQRS |
| ”삭제”가 실제 제거여도 무방 | 감사나 실행 취소를 위한 소프트 삭제 요구 |
| 드문 동시 편집 | 갱신 손실(lost update) 위험 → 낙관적 락, 버전 |
| ”이 레코드를 변경”이라는 동작 | 비즈니스 동사인 동작 → 워크플로에 이름 부여 |
마지막 행은 기억해 둘 만한 설계상의 경고 신호입니다. 엔드포인트 이름이 updateStatus 쪽으로 흘러가기 시작하면, 도메인이 자신만의 동사를 요구하고 있는 것입니다.
한계와 트레이드오프
- 갱신은 이력을 파괴합니다. 제자리 변경은 CRUD의 본질적인 동작이자 본질적인 손실입니다. “화요일에는 어떤 모습이었나”를 알아야 하는 모든 것에는 저널링이나 이벤트가 필요합니다.
- 삭제는 동사가 아니라 정책입니다. 소프트 삭제와 하드 삭제 사이의 선택은 감사 가능성을 개인정보 규제의 삭제 의무 및 쿼리 복잡성과 맞바꾸는 일입니다. 클래스별로, 의도적으로 결정해야 합니다.
- 동시성의 비용은 드러나지 않습니다. 두 갱신이 겹치면 마지막에 쓴 쪽이 이기고, 먼저 쓴 쪽의 변경은 조용히 사라집니다. 버전 필드와 조건부 저장이 표준 해법입니다.
- 목록 조회는 다섯 번째 동사입니다. 필터링, 정렬, 페이지네이션이 실제 읽기 트래픽의 대부분과 성능 버그의 대부분을 차지합니다. CRUDL과 BREAD가 존재하는 이유입니다.
- CRUD API는 빈약해질 수 있습니다. 행을 넣고 행을 꺼내는 것이 전부인 표면은 비즈니스 로직을 클라이언트로 밀어냅니다. 워크플로는 데이터 옆, 서버 측에 두어야 합니다.
Back4app에서의 CRUD
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. Back4app은 이 글에서 다룬 작업을 덜어 줍니다. 클래스를 정의하면 위의 CRUD 매트릭스 전체가 한 번에 생깁니다. HTTP 열에 해당하는 REST 엔드포인트, GraphQL 뮤테이션, 코드 탭의 SDK 관용구가 모두 갖춰지며, 클래스 수준 권한이 각 동사를 통제하고 beforeSave 트리거가 검증을 담당합니다. 네 가지 동사는 더 이상 코드베이스의 일부가 아니라 어휘가 됩니다.
자주 묻는 질문
CRUD는 무엇의 약자입니까?
Create, Read, Update, Delete(생성, 읽기, 갱신, 삭제)의 약자로, 영속 스토리지의 네 가지 기본 연산이자 데이터를 다루는 모든 애플리케이션이 지원해야 하는 최소한입니다. 이 약어는 James Martin이 1983년 저서 Managing the Data-base Environment에서 대중화했고, 관련 의미론은 1990년 Haim Kilov가 정형화했습니다. 그래서 백엔드 어휘 가운데 지금도 매일 쓰이는 가장 오래된 용어 중 하나입니다.
CRUD는 SQL과 HTTP에 어떻게 대응합니까?
모든 개발자가 한 번 외워 두는 깔끔한 대응 두 가지가 있습니다. SQL에서 생성은 INSERT, 읽기는 SELECT, 갱신은 UPDATE, 삭제는 DELETE입니다. HTTP에서 생성은 POST, 읽기는 GET, 갱신은 전체 교체라면 PUT, 부분 변경이라면 PATCH이며, 삭제는 DELETE입니다. 정상 경로의 상태 코드는 생성이 201, 읽기와 갱신이 200, 삭제가 204입니다.
CRUD와 REST는 같은 것입니까?
아니요. 둘은 서로 다른 질문에 답합니다. CRUD는 데이터에 무엇을 하는지를 가리키고, REST는 클라이언트가 HTTP로 리소스와 상호작용하는 방식에 관한 아키텍처 스타일로서 무상태, 균일한 인터페이스 같은 제약 조건을 따릅니다. REST 엔드포인트는 흔히 CRUD에 깔끔하게 대응하지만, REST는 CRUD가 아닌 동작도 노출할 수 있고, CRUD는 SQL 세션, SDK, 큐처럼 HTTP 밖에서도 문제없이 존재합니다.
PUT과 PATCH의 차이는 무엇입니까?
갱신의 범위입니다. PUT은 리소스 전체를 보낸 표현으로 교체합니다. 두 번 보내도 같은 상태가 되므로 정의상 멱등합니다. PATCH는 부분 수정을 적용하며, 보낸 필드만 바뀝니다. 실제 "갱신" 트래픽의 대부분은 PATCH 형태이고, SDK의 save() 메서드가 변경된 필드만 전송하는 것도 그 때문입니다.
CRUD 앱이란 무엇입니까?
레코드를 생성하고, 조회하고, 편집하고, 삭제하는 것이 핵심 루프인 애플리케이션입니다. 관리자 패널, CMS, CRM, 재고 및 예약 시스템이 대표적입니다. 다소 깎아내리는 뉘앙스로 쓰이지만 그럴 이유가 없습니다. 비즈니스 소프트웨어의 대다수는 본질적으로 CRUD이며, 바로 그렇기 때문에 CRUD 계층을 자동으로 생성하는 플랫폼이 그만큼 많은 작업을 덜어 줍니다.
소프트 삭제란 무엇입니까?
레코드를 실제로 제거하는 대신 플래그나 타임스탬프로 삭제되었다고 표시하는 방식입니다. 감사 이력, 참조 무결성, 실행 취소 가능성을 보존하는 대신, 모든 쿼리에 필터를 걸어야 하고 고유 제약 조건이 복잡해지는 비용을 치릅니다. 반대편인 하드 삭제는 실제 제거이며, GDPR 삭제 요청 같은 개인정보 규제가 이를 실제로 요구할 수 있습니다. 대부분의 시스템에는 일괄적인 기본값이 아니라 클래스별로 의도적으로 정한 정책이 필요합니다.
CRUD만으로 충분하지 않은 때는 언제입니까?
도메인이 현재 상태보다 이벤트와 이력에 관한 것일 때입니다. 제자리 갱신은 과거를 지웁니다. 이벤트 소싱은 추가 전용 로그를 유지하고 그로부터 상태를 도출하며, CQRS는 읽기 모델과 쓰기 모델을 완전히 분리합니다. 또한 "인보이스 승인"이나 "결제" 같은 비즈니스 동작은 행 편집이 아니라 워크플로이며, 이를 단순한 갱신으로 모델링하면 도메인이 가려집니다. CRUD는 데이터 접근의 바닥이지 천장이 아닙니다.
CRUDL이나 BREAD 같은 CRUD 변형은 무엇입니까?
같은 아이디어를 확장하거나 다르게 표기한 것입니다. CRUDL은 단일 레코드 읽기와 구분되는 별도 연산으로 List(목록 조회)를 추가하고, BREAD는 이를 Browse, Read, Edit, Add, Delete로 표기합니다. 필터링과 페이지네이션을 포함한 컬렉션 목록 조회가 단순한 "읽기의 복수형"이 아니라 고유한 설계 결정이 따르는 독립된 연산이라는 실무적 사실을 인정한 것입니다.