avlogo
Spider
Home
About
WorkSpace
Contact
Book a Call
avlogo

Aviralyadav Aviralyadav

I'm Aviral - a full-stack developer, freelancer & problem solver. Thanks for checking out my site!

Genral

  • Home
  • About Me
  • Projects

Specifics

  • Guest Book
  • Bucket List
  • Uses

Genral

  • Home
  • About Us
  • Projects

Specifics

  • Guest Book
  • Bucket List
  • Uses

Contact

  • Contact Us
  • Links
  • RSS

© 2025 Aviral Yadav. All rights reserved.

Privacy Policy•Terms of Use
Made withCodeForTravel
All Projects

Curated Work / planit

PlanitPlanit

Plan smarter — a focused, real-time task manager.

REACT.JSTAILWIND CSSMONGODBEXPRESSNODE.JSVERCEL
Type
Full-Stack Web App
Role
Full-Stack Developer
Timeline
2023
Status
Live
Planit screenshot 1
Planit screenshot 2

Overview

Planit is a task manager I built to be fast, personal, and deliberately opinionated — the anti-Jira. Every user signs in with Google and lands on a dashboard scoped entirely to them, where work is organized by priority and by custom categories they define themselves. Under the hood it's a React + Tailwind front end talking to an Express/MongoDB API that runs as a serverless function on Vercel, wrapped in an optimistic UI that makes every click feel instant even when the network isn't. I designed the whole thing around one principle: the app should never make you wait to think.

A modern, full-stack task management platform designed to help you plan smarter, stay organized, and boost productivity — a clean UI, smooth interactions, and secure authentication for a seamless daily workflow.

Key Features

  • Secure Google authentication with a personalized dashboard.
  • Create, manage, and track tasks with real-time MongoDB persistence.
  • Smart task organization with priorities and custom categories.
  • Clean, intuitive UI with smooth animations for a focused experience.
  • Fast performance and responsive layouts designed for productivity.

Why I Built This

I've bounced off nearly every 'powerful' task manager I've tried. They optimize for configurability — statuses, workflows, custom fields, automations — and in doing so they optimize away momentum. By the time I've set up the perfect board, I've lost the thing I actually wanted to do. Planit is my reaction to that: a tool with a small number of strong opinions and almost nothing to configure, so the fastest path is always just capturing the task and moving on.

I also wanted a project where I owned every layer of a real full-stack app end to end — Google auth, the data model, the serverless deployment, and especially perceived performance. A task app is deceptively good for this, because users interact with it constantly and notice any latency immediately. That made it the perfect place to get obsessive about optimistic updates and a data model that stays fast as it grows.

Architecture Overview

Planit is split into a React SPA and a stateless Express API, both deployed on Vercel. The client is a Vite build served as static assets; the API is the same Express app exported as a single serverless handler behind a catch-all rewrite. Auth is Google-first: the browser gets a Google ID token, the server verifies it and issues its own httpOnly session cookie, and every data query is scoped by the authenticated user's id so the dashboard is personal by construction.

planit/
├── client/                      # React + Vite + Tailwind
│   ├── src/
│   │   ├── components/          # TaskCard, PriorityFlag, CategoryPill
│   │   ├── features/tasks/      # useTasks(), optimistic mutations
│   │   ├── context/AuthContext.jsx
│   │   └── lib/api.js           # fetch wrapper, credentials: 'include'
│   └── vite.config.js
├── server/
│   ├── api/index.js             # Express app, exported as serverless handler
│   ├── models/                  # Task.js, Category.js, User.js  (Mongoose)
│   ├── routes/                  # tasks.js, categories.js, auth.js
│   ├── middleware/auth.js       # verifies session cookie -> req.user
│   └── db.js                    # cached Mongo connection (serverless-safe)
└── vercel.json                  # rewrites /api/* -> serverless function
  • Frontend — React + Vite + Tailwind, with TanStack Query owning all server state so caching, refetching, and optimistic mutations live in one place instead of scattered useEffect hooks.
  • Data layer — MongoDB via Mongoose, with Task, Category, and User collections. Every task carries a userId and a categoryId; queries filter on userId first, always.
  • Auth — Google Identity Services on the client, google-auth-library verifying the ID token server-side, then a signed httpOnly JWT cookie for the session.
  • Deployment — One Vercel project: static client build plus the Express API as a serverless function, with a cached Mongo connection so warm invocations skip the handshake.

Task Engine — Priorities & Categories

The core of Planit is a small task engine that keeps the dashboard sorted the way I actually think about work. Priority is a four-value enum (low, medium, high, urgent), but each level maps to a numeric weight so I can sort deterministically on the server. The default ordering is a composite: priority weight descending, then due date ascending, then creation time — so the most urgent, soonest-due work always floats to the top without the user touching a sort control.

Categories are the one place I let users customize. Each category is a user-owned document with a name and a color, and tasks reference it by id rather than duplicating the label. That keeps renames and recolors a single write instead of a fan-out across every task, and it lets the sidebar render category filters straight from the Category collection.

  • Deterministic sort — Priority stored as an enum for the API but weighted numerically for ordering, so the same composite sort runs identically on the server and in the optimistic client cache.
  • Referenced categories — Tasks hold a categoryId, not a copied label; a rename is one document update, and category colors drive the CategoryPill component directly.
  • Compound index — A { userId: 1, category: 1, priority: -1 } index backs the dashboard's most common query — 'my tasks in this category, most important first' — so it stays an index scan as data grows.
  • Filtered views — Category and priority filters compose into a single Mongo query rather than filtering client-side, keeping the payload small on larger accounts.

Optimistic UI Layer

Nothing in Planit shows a spinner for a task action. Creating, completing, editing, and deleting all apply to the local cache immediately and reconcile with the server afterward. I built this on TanStack Query's mutation lifecycle: onMutate cancels in-flight refetches, snapshots the current cache, and writes the optimistic change; onError rolls back to that snapshot; onSettled invalidates so the server's truth wins in the end.

The interesting edge case was creates. A new task has no server id yet, so I assign a temporary client id, render it instantly, and swap in the real id once the POST resolves — while making sure a fast follow-up action (like immediately completing that new task) queues correctly against the temp id. Getting that reconciliation right is what makes the app feel native rather than like a webpage waiting on a database.

  • Snapshot + rollback — Every mutation captures the pre-change cache so a failed request restores the exact prior state instead of a jarring refetch flash.
  • Temp-id reconciliation — Optimistic creates render with a client-generated id and swap to the server id on resolve, so subsequent actions don't target a ghost.
  • Motion on change — Framer Motion's layout animations and AnimatePresence handle reordering, filtering, and removal, so the optimistic state change and the animation are driven by the same source of truth.

Backend Systems

The API is a conventional REST surface — /tasks, /categories, /auth — kept intentionally thin because the client already owns most of the UX state. The work on the backend is in three places: verifying identity, scoping data, and surviving serverless cold starts. Auth middleware reads the session cookie, verifies the JWT, and hangs req.user off the request; from there every handler filters by req.user.id, so there's no way to read another user's tasks even with a guessed id.

  • Auth — Google ID token verified once at login with google-auth-library, then the app issues its own httpOnly, SameSite JWT cookie so I never handle passwords and every request carries a cheap-to-verify session.
  • Data access — Mongoose models with validation on priority enums and required userId, plus indexes tuned to the dashboard query rather than left to Mongo's defaults.
  • API — REST routes with a shared error handler that maps validation and auth failures to clean status codes, so the client's onError rollbacks trigger predictably.
  • Serverless connection — The Mongo connection is cached on the module scope and reused across warm invocations, avoiding a new handshake (and connection-pool exhaustion) on every request.

Key Decisions

  • MongoDB over Postgres — Tasks and user-defined categories are naturally document-shaped and I wanted to iterate on the schema fast. There are no heavy relational joins in the hot path, so a document store fit the access pattern better than SQL.
  • Google-only auth over email/password — Skipping passwords removed a whole class of security burden (hashing, resets, breaches) and cut onboarding to one click. The trade-off — requiring a Google account — is acceptable for the audience I built this for.
  • Optimistic UI over spinners — For an app people touch dozens of times a session, perceived latency matters more than architectural simplicity. I accepted the added complexity of rollbacks and temp-id reconciliation to make every action feel instant.
  • Express-on-Vercel over a separate host — Running the API as a serverless function keeps deployment to a single project and one CI pipeline. The cost was solving connection caching so cold starts and warm reuse both behave — a real gotcha I'd have avoided on a long-lived server.
  • Referenced categories over denormalized labels — Storing a categoryId instead of copying the label onto every task means renames and color changes are one write. It costs a small lookup to hydrate category metadata, which I do once per dashboard load.
  • Opinionated UX over configurability — I deliberately shipped fewer options — one sort, a fixed priority scale, minimal settings. That cut UI surface and edge cases dramatically and kept the product true to its point.

What I Learned

  • Serverless changes your data layer — The single biggest lesson was that Express in a serverless function is not Express on a server: without a cached Mongo connection you exhaust the pool fast. Getting that right taught me more about connection lifecycles than any long-running app would have.
  • Optimism is mostly about failure — The happy path of optimistic UI is trivial; the value is entirely in getting rollback and temp-id reconciliation right, which is where I spent most of the effort and learned the most.
  • Constraints are a feature — Choosing to make the app opinionated didn't just improve the product — it shrank the code, killed whole categories of edge cases, and made decisions faster for me as the developer.
  • Index for the query you actually run — Watching the dashboard query and building a compound index around it, instead of trusting defaults, was a small change with an outsized effect on how the app scales per user.

Tech Stack

REACT.JSTAILWIND CSSMONGODBEXPRESSNODE.JSVERCEL

Like what you see?

Explore more work or get in touch.

More ProjectsGet in Touch