The handler works in Postman, and there is still no command that fails on a broken save. You have a rough create endpoint that responds with 201 when you send a valid JSON body, but you have no way to confirm it actually saves the data without clicking through a UI or running manual curl requests every time you refactor. This guide will give you a working 4-route todo API, a test suite that runs in seconds, and a clear workflow to make sure you never push broken persistence code again.
Build Deliverables
✅ 4-route todo API with standardized request/response shapes

✅ 3 core integration tests that fail immediately on broken persistence
✅ Single test command you run before every commit
✅ Pre-commit hook that blocks commits if tests fail

✅ 1-line README instruction for running the full test suite
Routes a todo API needs before a UI exists
You do not need 12 fancy routes for a v0 todo API, only the four endpoints a basic UI will call to handle all core user actions. The table below defines the exact shape of each route, so you can build to a consistent specification and avoid rework when you add a UI later:
| HTTP Method | Route | Request Shape | Success Response | Common Error Codes |
|---|---|---|---|---|
| POST | `/todos` | `{ “content”: string, “completed”: boolean? }` | 201 Status + Full todo object (including unique ID) | 400 (missing content) |
| GET | `/todos` | No request body | 200 Status + Array of all saved todo objects | N/A |
| PATCH | `/todos/:id` | `{ “content”: string?, “completed”: boolean? }` | 200 Status + Updated todo object | 404 (ID not found), 400 (invalid field) |
| DELETE | `/todos/:id` | No request body | 204 Status (no content) | 404 (ID not found) |
All routes accept and return JSON, and follow standard REST conventions so any frontend developer familiar with REST can integrate with your API without extra documentation.
Test file that fails if create does not persist
We will use JavaScript, Express, and Jest for this example, but the same pattern works for any language or test runner. The goal is to write integration tests that hit your live API endpoints and validate real behavior, not isolated unit tests that mock core persistence logic. Create a file named `todo.test.js` in your project root with the following code:
“`javascript
const request = require(‘supertest’);
const app = require(‘./app’);
const store = require(‘./store’);
// Wipe all data before every test to eliminate cross-test pollution
beforeEach(() => {
store.reset();
});
test(‘create endpoint persists todos to the store’, async () => {
const newTodo = { content: ‘Write test suite’, completed: false };
// Send create request to API
const createResponse = await request(app).post(‘/todos’).send(newTodo);
expect(createResponse.statusCode).toBe(201);
const createdTodo = createResponse.body;
// Fetch all todos to confirm the new entry was saved
const getResponse = await request(app).get(‘/todos’);
expect(getResponse.statusCode).toBe(200);
expect(getResponse.body).toEqual(expect.arrayContaining([createdTodo]));
});
test(‘patch endpoint updates persisted todos’, async () => {
const initialTodo = await store.create({ content: ‘Old content’, completed: false });
const updateResponse = await request(app)
.patch(`/todos/${initialTodo.id}`)
.send({ completed: true });
expect(updateResponse.statusCode).toBe(200);
expect(updateResponse.body.completed).toBe(true);
// Confirm the update is saved, not just returned in the response
const updatedTodo = await store.getById(initialTodo.id);
expect(updatedTodo.completed).toBe(true);
});
test(‘delete endpoint removes todos from the store’, async () => {
const todoToDelete = await store.create({ content: ‘Delete me’, completed: false });
const deleteResponse = await request(app).delete(`/todos/${todoToDelete.id}`);
expect(deleteResponse.statusCode).toBe(204);
// Confirm the todo is no longer present
const allTodos = await store.getAll();
expect(allTodos).not.toEqual(expect.arrayContaining([todoToDelete]));
});
“`
Example measurement: this test suite runs in 1.2 seconds on a 2022 laptop, so you will never avoid running it because it takes too long. If your create handler stops saving data for any reason, the first test will fail immediately, no manual Postman checks required.
In-memory store you throw away on process exit
For v0 development, you do not need a full PostgreSQL or MongoDB instance to test persistence. A simple in-memory store that resets every time your server restarts is enough to build and test all core API logic, and you can swap it out for a real database later without changing your test suite. Create a file named `store.js` with the following code:
“`javascript
let todos = [];
let nextId = 1;
module.exports = {
create: (todoData) => {
const todo = { id: nextId++, …todoData };
todos.push(todo);
return todo;
},
getAll: () => todos,
getById: (id) => todos.find(t => t.id === Number(id)),
update: (id, updateData) => {
const index = todos.findIndex(t => t.id === Number(id));
if (index === -1) return null;
todos[index] = { …todos[index], …updateData };
return todos[index];
},
delete: (id) => {
const index = todos.findIndex(t => t.id === Number(id));
if (index === -1) return false;
todos.splice(index, 1);
return true;
},
reset: () => {
todos = [];
nextId = 1;
}
};
“`
This store has zero dependencies, and the exposed `reset` method makes it trivial to clean up state between tests. When you are ready to add a production database, you can rewrite these methods to call your database instead of modifying the in-memory array, and your existing test suite will validate that the new database integration works exactly like the old in-memory store.
Green-main rule before the next feature branch
This non-negotiable rule eliminates almost all accidental broken code pushes to your main branch: you never push code to main unless all tests pass, and you never create a new feature branch off a main branch with failing tests. To enforce this, add a pre-commit git hook that runs your test command automatically before every commit, and blocks the commit if any test fails.
You can use a tool like Husky for Node projects, or add a simple bash script to your `.git/hooks/pre-commit` file that runs `npm test` and exits with a non-zero code if the tests fail. You can bypass the hook in an emergency for critical bug fixes, but you should always run the tests immediately after committing to avoid breaking the main branch. Illustrative example: adding a due date field to todos only requires adding one new test to confirm the due date is persisted, and the existing tests will continue to validate all existing functionality.
README line that shows how to run the suite
Many projects hide test instructions behind paragraphs of complex setup steps, but your README should have a single, easy-to-find line right under the installation instructions that tells anyone working on the project how to run the full test suite. The exact line should be:
Run `npm test` to execute the full API test suite and confirm all persistence logic works as expected.
Add a 1-sentence note below it clarifying that the tests run against an in-memory store, so no external dependencies like databases or Docker are required to run them. If you come back to this project in 6 months, you will not have to dig through old code or notes to remember how to validate that your API works.
Open your existing todo API project, add the create persistence test first, and run it three times in a row to confirm it fails when you break the save logic and passes when the functionality works.
Written by the Build Next Stack editors.