Stop 19 · Build Full-Stack Portfolio ProjectsProjects · 9/12

Project Management Tool

Part of the Full Stack Developer Roadmap

In short

Boards, columns, cards you drag around, and teammates seeing your moves live. A Trello-style tool shows you can handle nested data, ordering and real-time sync in one app.

A Trello/Asana-style app — boards, tasks, assignees and deadlines — a feature-rich project demonstrating complex, deeply nested data and real-time-feeling collaboration.

The purple blob moves coloured sticky notes across a cork board split into columns, with balloons nearby.

Think of it like this

The fest committee's sticky wall

Before the college fest, the committee keeps a wall with three columns: to do, doing, done. Each sticky note is a task. Move one, and everyone in the room sees it moved. Your app is that wall, shared online.

Watch it happen

Dragging a card while a teammate watches

Your tab
API
Database
Teammate's tab
drop card 7

1/5You drop a card between positions 1 and 2 in 'Doing'. The UI moves it at once.

The big ideas

What you need to know

Nested but flat in SQL

Workspace, board, column, card as separate tables with foreign keys. Fetch a whole board in one or two queries.

Order with numbers

Give each card a numeric position. Moving between 1 and 2 means 1.5, so one row changes.

Live sync per board

Each board is a room. Broadcast moves, edits and comments only to people viewing it.

Board membership

Every card route checks the user belongs to the card's board, not just that they're logged in.

See it in code

Walk through the code

ts
1// Positions are numbers. Moving between 1 and 2? Use 1.5. No other rows change.2function positionBetween(before?: number, after?: number) {3  if (before === undefined && after === undefined) return 1;4  if (before === undefined) return after! - 1;5  if (after === undefined) return before + 1;6  return (before + after) / 2;7}8 9app.patch("/api/cards/:id", requireBoardMember, async (req, res) => {10  const { columnId, beforePos, afterPos } = req.body;11  const card = await db.card.update({ where: { id: req.params.id },12    data: { columnId, position: positionBetween(beforePos, afterPos) } });13  io.to(`board:${card.boardId}`).emit("card:moved", card);14  res.json(card);15});

1/3Pick a number between the neighbours, or one past the end if dropped at the top or bottom.

Fractional positions make a drag one small UPDATE. After saving, the server tells everyone else on the board.

Save yourself the pain

Common mistakes

Oops

Renumbering every card in the column (1, 2, 3, …) after each drag.

Do this instead

Use fractional positions, and only renumber a column in the rare case numbers get too close.

Oops

Fetching each column's cards in a separate request.

Do this instead

Load the whole board in one request: columns with their cards, already sorted.

Your mission

First milestone: drag and persist

~45 min

Quick check

Cards sit at positions 1, 2 and 3. You drag the card at 3 between the first two. With fractional ordering, what changes?

Go deeper

  1. 1Support drag-and-drop task reordering across columns.
  2. 2Add task assignees, due dates and an activity log.
  3. 3Support multiple boards or projects per user/team.

More projects at this stop

Stuck on this topic? Ask an Insider

Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.

Download
InsideEdge