Practical Workshop: Coding with Cursor
This section is the hands-on part of the workshop.
Participants build a tiny feature end-to-end and experience a safe, structured workflow with Cursor.
The example is intentionally small so we can focus on workflow and habits, not on framework details.
1. Setup
What you need
- Node 18+ installed.
- Cursor installed and logged in.
- Git + a local clone of the workshop repo.
Get started
cd examples/express-todo-ts
pnpm install
Orientation
The project is a tiny in-memory Todo API built with Express + TypeScript:
examples/express-todo-ts/
src/
models/todo.ts # Todo type definition
routes/todos.ts # Route handlers (GET, POST)
services/todoService.ts # In-memory store + helper functions
app.ts # Express app setup
server.ts # Entry point
tests/
todos.test.ts # Jest + supertest tests
Existing endpoints:
GET /todos– list all todos from the in-memory store.POST /todos– create a todo withtitleandcompleted = false.
2. The Feature
We extend the existing Todo API with a new endpoint:
POST /todos/:id/complete – mark a todo as completed.
| Scenario | Response |
|---|---|
| Todo exists | Set completed = true, return the updated todo (200 OK) |
| Todo does not exist | Return 404 |
| Already completed | Return the current state unchanged (idempotent) |
We build this using Cursor in a disciplined workflow:
- Understand the existing code.
- Write tests first.
- Generate implementation with AI.
- Review, run tests, and commit.
3. Step-by-Step Workshop Flow
Step 0 – Open in Cursor
- Open
examples/express-todo-tsin Cursor. - Point out the project structure:
src/models,src/routes,src/services,tests/. - Note that this project has no
.cursorrulesorAGENTS.mdyet – the AI has zero project-specific context.
Talking point: “Notice how there are no rules files. The AI doesn’t know our conventions. Keep that in mind as you review what it generates.”
Step 1 – Explore with Ask mode
In Cursor chat (Ask mode), run:
We're in the Express + TypeScript project at @examples/express-todo-ts.
Question:
- Where is the Todo API implemented?
Please:
- List the main files (models, routes, services, tests).
- Summarize in 3-5 bullet points how it currently works.
- Do not change any code yet.
Open the files Cursor mentions:
src/models/todo.tssrc/routes/todos.tssrc/services/todoService.tstests/todos.test.ts
Teaching points:
- Use
@-mentions (@src,@tests) to give AI better context. - Verify what AI says by reading the files it references.
- AI can summarize incorrectly – always check.
Step 2 – Write failing tests first
In chat (Plan or Agent mode):
We want to add a feature to the Express + TypeScript Todo API at @examples/express-todo-ts:
- Endpoint: POST /todos/:id/complete
- Behavior:
- If the todo exists, set completed = true and return the updated todo (200 OK).
- If the todo does not exist, return 404.
- If the todo is already completed, just return the current state (idempotent).
Stack:
- Express 4
- TypeScript
- Existing tests under @examples/express-todo-ts/tests using Jest + supertest.
Tasks:
1. Propose 3-5 Jest test cases for this behavior.
2. Write the test functions into the appropriate test file, matching the existing style.
3. Stop after the tests -- do not implement the endpoint yet.
Walk participants through:
- Where the tests were added (
tests/todos.test.ts). - How the tests document the intended behavior of
POST /todos/:id/complete.
Run pnpm test and show that the new tests fail. That’s the point – tests define the spec before any implementation exists.
Step 3 – Implement to make tests pass
Once tests are failing:
Now implement the feature in the Express + TypeScript project so that the new tests pass.
Context:
- Routes live in @examples/express-todo-ts/src/routes/todos.ts
- The in-memory store and helper functions live in @examples/express-todo-ts/src/services/todoService.ts
Constraints:
- Keep existing behavior of the Todo API unchanged.
- If you need to touch multiple files, list them first.
- Prefer small, focused changes.
You can demonstrate either:
- Inline edit (
Cmd/Ctrl+K) directly insidesrc/routes/todos.tsandsrc/services/todoService.ts. - Composer / Agent mode for a multi-file change with plan-review-execute discipline.
Review the proposed changes together:
- Does it satisfy the tests?
- Does it duplicate logic that already exists?
- Any obvious security or performance issues?
- Could you explain this in a code review?
Apply the edits and run pnpm test again. All tests should pass.
Step 4 – Review and commit
Review checklist:
- All tests pass (old + new)
- Implementation matches the spec
- No unrelated changes crept in
- No security issues (auth, validation, logging)
- Could I explain this in a code review?
Commit small and descriptive:
git add tests/todos.test.ts
git commit -m "test: add tests for POST /todos/:id/complete"
git add src/
git commit -m "feat: implement todo completion endpoint"
4. Refactoring Exercise
After Step 3, review the AI-generated code. There’s a good chance the AI put the lookup-and-mutate logic directly in the route handler – something like:
todosRouter.post("/:id/complete", (req, res) => {
const todo = listTodos().find((t) => t.id === id);
todo.completed = true;
return res.status(200).json(todo);
});
This works, but it violates separation of concerns. The route handler is doing work that belongs in the service layer.
The prompt
Ask participants to refactor using AI:
We want to improve the design of the Express + TypeScript Todo API at @examples/express-todo-ts:
Current situation:
- The POST /:id/complete route handler in src/routes/todos.ts does its own array
lookup and mutation instead of calling a service function.
- Business logic for completing a todo lives in the route, not the service layer.
Goal:
- Keep route handlers thin.
- Add a completeTodo(id: string) function in src/services/todoService.ts.
- Update the route to call completeTodo() instead of doing the lookup inline.
- Keep behavior and tests unchanged.
Tasks:
1. Propose the new completeTodo() function.
2. Update the route to use it.
3. Verify tests still pass.
Discussion points
After the refactor, discuss:
- Is the new structure easier to test in isolation?
- Would a new developer find the behavior faster in the service layer or the route?
- How hard would it be to swap the in-memory store for a real database now?
- Which “golden rules” did this refactor reinforce? (Small steps, mandatory review, better context.)
5. Reflection & Discussion
After the feature works, debrief with the group:
- What felt fast?
Boilerplate, test scaffolding, repetitive patterns. - Where did human judgment matter most?
Deciding the endpoint behavior and constraints. Reviewing the implementation and edge cases. - Which rules from the “Twelve Golden Rules” did we actually use?
Context-first prompting. Iterative approach (design -> tests -> code). Mandatory code review.
Ask participants to name at least one place where AI would have gone wrong if nobody reviewed the code.
6. Optional Extensions (for longer workshops)
If you have more time, each extension reinforces the same core habits: think first, prompt with context, keep steps small, review everything.
Error handling & logging
- Add structured logs to the completion endpoint.
- Use AI to propagate consistent logging across all routes.
Security checks
- Require authentication for the
/completeendpoint. - Ask AI for potential security pitfalls in the new code.
Input validation
- What happens if
:idis not a valid format? - Add validation and appropriate error responses.
Database migration
- Replace the in-memory store with SQLite or a similar lightweight database.
- See how the service-layer abstraction (from the refactoring exercise) makes this easier.