Background Mobile

How to Make an App Like Asana

mobile app/
September 17, 2026
How to Make an App Like Asana

Building a project management tool is deceptively hard. The interface looks simple — tasks, boards, timelines — but the system underneath has to handle real-time collaboration, fine-grained permissions, complex dependency graphs, and notification pipelines that don't fall over when you have 50,000 concurrent users. This post walks through the architecture decisions that matter when you're building something in this space.

What Does an App Like Asana Actually Do at a System Level?

Before writing a line of code, you need to be honest about what you're building. Asana's core data model is a directed acyclic graph (DAG) of tasks, projects, portfolios, and workspaces. Tasks can belong to multiple projects. They have assignees, followers, dependencies, custom fields, and attachments. Every mutation on this graph needs to be reflected in real time to every connected client.

That last part is where most teams underestimate scope.

Asana's engineering team has written publicly about using a custom operational transform (OT) system and later moving toward CRDT-like approaches for conflict resolution. You don't need to replicate their exact solution, but you do need to pick a side: are you using WebSockets with server-authoritative state, or are you going with a CRDT library like Yjs or Automerge and accepting the complexity that comes with peer-reconciliation?

For most teams building a first version, a WebSocket layer backed by Redis Pub/Sub (or a managed equivalent like Ably or Pusher) is the right call. CRDTs pay off when you have a meaningful offline-first requirement. If your users are primarily online and on a stable connection, the added complexity doesn't earn its keep.

The Core Data Model

Your entity hierarchy will look roughly like this:

  • Workspace (top-level tenant boundary)
  • Project (belongs to workspace, has members, settings, view preferences)
  • Section (ordered container within a project)
  • Task (belongs to one or more projects, has assignee, due date, priority, custom fields)
  • Subtask (a Task whose parent is another Task — limit depth to avoid recursive query nightmares)
  • Comment / Activity feed (append-only log per task)

Custom fields are the tricky part. Asana supports text, number, dropdown, date, and people fields that can be scoped to a workspace or a project. You'll want a flexible schema here — either a JSONB column in PostgreSQL with typed validation at the application layer, or a proper EAV (Entity-Attribute-Value) table if you need to query across custom fields efficiently. JSONB + GIN index is usually fine up to a few million rows. Beyond that, you'll feel it.

What Tech Stack Should You Use?

There's no single right answer, but here's what works and why.

Backend: Node.js (with TypeScript) or Go. Node wins on ecosystem maturity for real-time tooling. Go wins on raw performance per core and is worth it if you're expecting serious concurrency from day one. FastAPI in Python is a reasonable choice if your team is Python-heavy, but the async ecosystem for WebSockets is less battle-tested than Node's.

Database: PostgreSQL for the primary store. You will want Redis for caching hot data (user sessions, project member lists) and as your Pub/Sub backbone for real-time events.

Search: Elasticsearch or OpenSearch if you need full-text search across task titles, descriptions, and comments. For a v1, PostgreSQL's tsvector can carry you surprisingly far.

Frontend: React with a state management layer that can handle optimistic updates. Zustand or Jotai over Redux for new projects — the boilerplate-to-benefit ratio of Redux is hard to justify in 2024. For the drag-and-drop board view, @dnd-kit/core is the current best option. The older react-beautiful-dnd is effectively unmaintained.

Real-time layer: Socket.io over raw WebSockets for the operational simplicity. If you need to scale horizontally, use socket.io-redis-adapter to broadcast events across instances.

File storage: S3-compatible object storage (AWS S3, GCS, or Cloudflare R2 depending on your cost profile) with pre-signed URLs for direct browser uploads. Don't route file bytes through your API server.

/// Not sure where to start?

Get the architecture before you commit

Tell us what you're building and we'll map the technical approach, stack, and rough timeline. No cost, no obligation, no sales call required.

How Do You Handle Permissions Without Making It Unmaintainable?

Permissions in a workspace tool are a genuine systems problem. You have workspace-level roles, project-level roles, and task-level visibility. They interact with each other in ways that are easy to get wrong.

Asana uses a model where workspace membership is the outer gate, and project membership controls what you can see and edit within that workspace. Tasks inherit project permissions by default.

The pragmatic implementation is RBAC (Role-Based Access Control) with four roles: Guest, Member, Admin, Owner. Layer project-level overrides on top. Avoid building a fully custom ABAC (Attribute-Based Access Control) system unless you have a specific enterprise compliance requirement driving it — you'll spend months on it and most users won't notice.

Use a policy table in PostgreSQL that maps (user_id, resource_type, resource_id, permission) rather than embedding permission logic in your application code. This keeps it auditable and testable.

For the API, every endpoint that returns a resource needs to call through a permission check service before returning data. This sounds obvious, but it's where horizontal privilege escalation bugs live. Centralise this logic in a single middleware layer, not scattered across controllers.

Notifications and Activity Feeds

The activity feed is an append-only event log. Each task mutation (created, assigned, commented, completed, due date changed) writes an event. The feed reads from this log.

Notifications are a separate concern. You need a fanout system: when a task is updated, identify all followers, then queue a notification job per follower. Use a job queue (BullMQ on Redis, or a managed queue like AWS SQS) rather than doing this synchronously. An email notification should never block an API response.

Respect notification preference settings at the user level. Most teams build this too late and end up with user complaints about email volume before they've built the preference UI.

Scaling Considerations You'll Hit in Production

Most of these won't matter until you're past a few thousand active users, but they're worth knowing before you make irreversible architectural choices.

Database connections: PostgreSQL's connection limit is a real ceiling. Use PgBouncer in transaction pooling mode from the start. Don't wait until you're hitting too many clients errors.

WebSocket scaling: Each WebSocket connection is a stateful connection to a specific server instance. When you scale horizontally, you need a shared event bus (Redis Pub/Sub) so that an event originating on server A propagates to clients connected to server B. Socket.io's Redis adapter handles this. Alternatively, look at purpose-built managed services like Ably, which offloads the entire real-time infrastructure.

Search latency: Full-text search on PostgreSQL starts to degrade around 10–20 million rows depending on your hardware. Plan your migration path to Elasticsearch/OpenSearch before you need it.

Timeline / Gantt view: Rendering dependency graphs for timeline views is CPU-intensive on the client side. Pre-compute critical path data server-side and cache it. Don't send the raw graph to the client and compute there.

Feature Pragmatic First Choice When to Revisit
Real-time sync WebSocket + Redis Pub/Sub Offline-first requirement
Custom fields PostgreSQL JSONB >5M rows with cross-field queries
Notifications BullMQ + SES >100k DAU
Search PostgreSQL tsvector >10M indexed documents
Permissions RBAC with policy table Complex enterprise compliance

Conclusion

Building a project management tool is a reasonable engineering challenge, not an impossible one. The real work is in the data model, the real-time layer, and the permissions system. Get those three right and the rest follows. Get them wrong early and you'll rewrite them under load.

If you're scoping this out and want a second opinion on the architecture before committing to an approach, that's exactly the kind of conversation we have at Sodio. We've built workflow and collaboration tooling across several verticals and the tradeoffs are usually more navigable than they first appear.


FAQ

How long does it take to build a project management app like Asana? A functional MVP with task management, project boards, basic real-time updates, and user roles typically takes 4 to 6 months with a team of 3 to 5 engineers. A production-ready system with search, timeline views, notifications, and enterprise permissions is a 12 to 18 month build.

What's the most expensive feature to build in a tool like this? Real-time collaboration and the activity/notification system together consume the most engineering time. They touch every part of the stack and require careful thought around consistency guarantees. Custom fields are a close second if you need them to be queryable and filterable at scale.

Should you build on top of an existing platform or from scratch? Existing platforms like Monday.com or ClickUp have white-label or API-extensible tiers. If your differentiation is domain-specific logic rather than the collaboration layer itself, buying the base and building on top of it is worth serious consideration. Building from scratch is justified when you need deep integration with proprietary data or workflows that off-the-shelf tools can't expose.

What database should you use for a task management app? PostgreSQL is the right choice for the primary store in almost every case. It handles the relational structure of tasks, projects, and permissions well, supports JSONB for flexible custom fields, and has a mature ecosystem. Add Redis for caching and Pub/Sub. Introduce Elasticsearch only when full-text search requirements outgrow what PostgreSQL's tsvector can handle.

How do you handle multi-tenancy in a workspace app? The standard approach is a shared database with a workspace_id foreign key on every tenant-scoped table, combined with row-level security (RLS) enforced either in PostgreSQL directly or in your application's query layer. Full schema-per-tenant isolation is operationally expensive and only worth it for strict data residency or compliance requirements.

Have a project in mind? Contact Sodio Technologies to discuss your requirements and explore the right technology solution for your business.

/// Work with us

Talk to the engineers who'd build it

You'll get a technical scope, timeline and cost estimate from the people doing the work, not an account manager. In-house team, no subcontracting, since 2016.

Contact Us