CRUD API Example
This example demonstrates mockd’s stateful mocking feature to simulate a complete CRUD (Create, Read, Update, Delete) API.
Overview
Section titled “Overview”We’ll create a mock API for a task management system with:
- Tasks (main resource)
- Users (for assignment)
- In-memory state across requests (resets to seed data on restart)
Configuration
Section titled “Configuration”Create tasks-api.yaml:
version: "1.0"
serverConfig: httpPort: 4280 adminPort: 4290
tables: - name: users seedData: - id: "1" name: "Alice" email: "alice@example.com" - id: "2" name: "Bob" email: "bob@example.com" - name: tasks seedData: - id: "1" title: "Setup project" description: "Initialize the project structure" status: "done" assigneeId: 1 createdAt: "2024-01-10T09:00:00Z" - id: "2" title: "Write documentation" description: "Create user documentation" status: "in_progress" assigneeId: 2 createdAt: "2024-01-11T10:00:00Z" - id: "3" title: "Add tests" description: "Write unit tests" status: "todo" assigneeId: null createdAt: "2024-01-12T11:00:00Z"
mocks: - id: health-check type: http name: Health check http: matcher: { method: GET, path: /health } response: statusCode: 200 body: '{"status": "ok", "timestamp": "{{now}}"}'
# Users CRUD - id: list-users type: http http: matcher: { method: GET, path: /api/users } response: { statusCode: 200 } - id: create-user type: http http: matcher: { method: POST, path: /api/users } response: { statusCode: 201 } - id: get-user type: http http: matcher: { method: GET, path: /api/users/{id} } response: { statusCode: 200 } - id: update-user type: http http: matcher: { method: PUT, path: /api/users/{id} } response: { statusCode: 200 } - id: delete-user type: http http: matcher: { method: DELETE, path: /api/users/{id} } response: { statusCode: 200 }
# Tasks CRUD - id: list-tasks type: http http: matcher: { method: GET, path: /api/tasks } response: { statusCode: 200 } - id: create-task type: http http: matcher: { method: POST, path: /api/tasks } response: { statusCode: 201 } - id: get-task type: http http: matcher: { method: GET, path: /api/tasks/{id} } response: { statusCode: 200 } - id: update-task type: http http: matcher: { method: PUT, path: /api/tasks/{id} } response: { statusCode: 200 } - id: patch-task type: http http: matcher: { method: PATCH, path: /api/tasks/{id} } response: { statusCode: 200 } - id: delete-task type: http http: matcher: { method: DELETE, path: /api/tasks/{id} } response: { statusCode: 200 }
extend: # Users - { mock: list-users, table: users, action: list } - { mock: create-user, table: users, action: create } - { mock: get-user, table: users, action: get } - { mock: update-user, table: users, action: update } - { mock: delete-user, table: users, action: delete } # Tasks - { mock: list-tasks, table: tasks, action: list } - { mock: create-task, table: tasks, action: create } - { mock: get-task, table: tasks, action: get } - { mock: update-task, table: tasks, action: update } - { mock: patch-task, table: tasks, action: patch } - { mock: delete-task, table: tasks, action: delete }Start the Server
Section titled “Start the Server”mockd start --config tasks-api.yamlAPI Operations
Section titled “API Operations”List All Tasks
Section titled “List All Tasks”curl http://localhost:4280/api/tasksResponse (paginated envelope):
{ "data": [ { "id": "1", "title": "Setup project", "description": "Initialize the project structure", "status": "done", "assigneeId": 1, "createdAt": "2024-01-10T09:00:00Z" }, { "id": "2", "title": "Write documentation", "description": "Create user documentation", "status": "in_progress", "assigneeId": 2, "createdAt": "2024-01-11T10:00:00Z" }, { "id": "3", "title": "Add tests", "description": "Write unit tests", "status": "todo", "assigneeId": null, "createdAt": "2024-01-12T11:00:00Z" } ], "meta": { "total": 3, "limit": 100, "offset": 0, "count": 3 }}Filter Tasks
Section titled “Filter Tasks”# By statuscurl "http://localhost:4280/api/tasks?status=todo"
# By assigneecurl "http://localhost:4280/api/tasks?assigneeId=1"Get Single Task
Section titled “Get Single Task”curl http://localhost:4280/api/tasks/2Response:
{ "id": "2", "title": "Write documentation", "description": "Create user documentation", "status": "in_progress", "assigneeId": 2, "createdAt": "2024-01-11T10:00:00Z"}Create Task
Section titled “Create Task”curl -X POST http://localhost:4280/api/tasks \ -H "Content-Type: application/json" \ -d '{ "title": "Review PR", "description": "Review pull request #42", "status": "todo", "assigneeId": 1 }'Response:
{ "id": "4", "title": "Review PR", "description": "Review pull request #42", "status": "todo", "assigneeId": 1}Update Task
Section titled “Update Task”curl -X PUT http://localhost:4280/api/tasks/4 \ -H "Content-Type: application/json" \ -d '{ "title": "Review PR", "description": "Review pull request #42", "status": "in_progress", "assigneeId": 1 }'Partial Update (PATCH)
Section titled “Partial Update (PATCH)”curl -X PATCH http://localhost:4280/api/tasks/4 \ -H "Content-Type: application/json" \ -d '{"status": "done"}'Delete Task
Section titled “Delete Task”curl -X DELETE http://localhost:4280/api/tasks/4Response: 204 No Content
Verify deletion:
curl http://localhost:4280/api/tasks/4Response: 404 Not Found
User Operations
Section titled “User Operations”List Users
Section titled “List Users”curl http://localhost:4280/api/usersCreate User
Section titled “Create User”curl -X POST http://localhost:4280/api/users \ -H "Content-Type: application/json" \ -d '{"name": "Charlie", "email": "charlie@example.com"}'State Management
Section titled “State Management”View Registered Resources
Section titled “View Registered Resources”curl http://localhost:4290/state/resourcesList Items in a Resource
Section titled “List Items in a Resource”curl http://localhost:4290/state/resources/tasks/itemsReset State
Section titled “Reset State”# Reset a specific resource to its seed datacurl -X POST http://localhost:4290/state/resources/tasks/reset
# Clear all items from a resource (does NOT restore seed data)curl -X DELETE http://localhost:4290/state/resources/tasksImport Stateful Resources
Section titled “Import Stateful Resources”Register new stateful resources via config import:
curl -X POST http://localhost:4290/config \ -H "Content-Type: application/json" \ -d '{ "config": { "statefulResources": [{ "name": "tasks", "idField": "id", "seedData": [ {"id": "1", "title": "Fresh task", "status": "todo"} ] }] } }'State Lifecycle
Section titled “State Lifecycle”Stateful resource definitions (name, seedData) are persisted to the admin file store and survive restarts. However, runtime data (items created, updated, or deleted via CRUD operations) is held in memory only. When the server restarts, runtime data resets to the seed data.
Workflow Example
Section titled “Workflow Example”Simulate a complete workflow:
# 1. Create a new taskTASK=$(curl -s -X POST http://localhost:4280/api/tasks \ -H "Content-Type: application/json" \ -d '{"title": "New feature", "status": "todo"}')TASK_ID=$(echo $TASK | jq -r '.id')
# 2. Assign to usercurl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \ -H "Content-Type: application/json" \ -d '{"assigneeId": 1}'
# 3. Start workcurl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \ -H "Content-Type: application/json" \ -d '{"status": "in_progress"}'
# 4. Complete taskcurl -X PATCH http://localhost:4280/api/tasks/$TASK_ID \ -H "Content-Type: application/json" \ -d '{"status": "done"}'
# 5. Verifycurl http://localhost:4280/api/tasks/$TASK_IDIntegration with Tests
Section titled “Integration with Tests”JavaScript/Node.js
Section titled “JavaScript/Node.js”const API = 'http://localhost:4280/api';
describe('Tasks API', () => { beforeEach(async () => { // Reset all resources to seed data before each test await fetch('http://localhost:4290/state/reset', { method: 'POST' }); });
test('create and list tasks', async () => { // Create task const createRes = await fetch(`${API}/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Test task', status: 'todo' }) }); const task = await createRes.json(); expect(task.id).toBeDefined();
// List tasks — response is a paginated envelope with data + meta const listRes = await fetch(`${API}/tasks`); const result = await listRes.json(); expect(result.data).toHaveLength(4); // 3 seed + 1 created expect(result.meta.total).toBe(4); expect(result.data.find(t => t.title === 'Test task')).toBeDefined(); });
test('update task status', async () => { // Create const createRes = await fetch(`${API}/tasks`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Task', status: 'todo' }) }); const { id } = await createRes.json();
// Update await fetch(`${API}/tasks/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'done' }) });
// Verify const getRes = await fetch(`${API}/tasks/${id}`); const task = await getRes.json(); expect(task.status).toBe('done'); });});Python
Section titled “Python”import requests
API = 'http://localhost:4280/api'ADMIN = 'http://localhost:4290'
def test_task_crud(): # Reset all resources to seed data requests.post(f'{ADMIN}/state/reset')
# Create task = requests.post(f'{API}/tasks', json={ 'title': 'Test task', 'status': 'todo' }).json() assert 'id' in task
# Read fetched = requests.get(f'{API}/tasks/{task["id"]}').json() assert fetched['title'] == 'Test task'
# Update requests.patch(f'{API}/tasks/{task["id"]}', json={ 'status': 'done' }) updated = requests.get(f'{API}/tasks/{task["id"]}').json() assert updated['status'] == 'done'
# Delete resp = requests.delete(f'{API}/tasks/{task["id"]}') assert resp.status_code == 204
# Verify deleted resp = requests.get(f'{API}/tasks/{task["id"]}') assert resp.status_code == 404Next Steps
Section titled “Next Steps”- Integration Testing - More testing patterns
- Stateful Mocking Guide - Full reference
- Admin API - State management API