React Query, Serverless API, & Express Reference Backend - React, Vite, JavaScript Fundamental Project 15
This repository bundles two complementary projects for learning and instruction: a production-ready Task Bud app (React + Vite + serverless API) and an Express.js reference backend. Use it to explore full-stack patterns, code organization, and how to run the same API as serverless (Vercel) or as a traditional Node server as educational learning tutorial purpose. (task-manager folder is the main project which is running currently on vercel serverless api)
- Live Demo: https://task-manager-react-query.vercel.app/
- Introduction
- Task Manager (Task Bud – Frontend + Serverless API)
- Task Manager Backend Reference (Express.js)
- License
- Happy Coding!
This monorepo contains:
-
task-manager/– Task Bud: A full-stack task manager with a Vite + React frontend and colocated Vercel serverless API under/api/tasks. It uses React Query for data fetching, caching, and optimistic updates, plus browserlocalStoragefor instant hydration. Ideal for learning serverless + React patterns and for deploying a single app to Vercel. -
task-manager-backend-reference/– A standalone Express.js backend that implements the same REST API. It is not used in production deployment but is kept for education, local development with a traditional server, and as a reference for scaling to a dedicated backend (e.g., adding a database or auth).
Both the serverless API and the Express backend expose the same contract (GET/POST /api/tasks, PATCH/DELETE /api/tasks/:id), so you can switch the frontend between them by changing the API base URL.
- Vite-powered React app with real-time task management (add, toggle complete, delete).
- Serverless REST API under
/api/tasks(Vercel serverless functions intask-manager/api/). - React Query for fetching, caching, optimistic updates, and minimal refetches.
- localStorage sync so tasks appear instantly on load and survive page refreshes.
- Toast notifications (React Toastify) for success and error feedback.
- Seed data from
api/tasks.data.jsonso the list is non-empty on cold start. - ESLint for consistent code quality;
npm run lintandnpm run lint:fixavailable.
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite 7, React Query (@tanstack/react-query), React Toastify, Axios |
| API | Node.js serverless functions (Vercel-style handlers in api/tasks/) |
| Storage | In-memory store (with optional REMOTE_TASKS_API), seed from api/tasks.data.json |
| Utilities | nanoid (IDs), browser localStorage |
| Tooling | ES Modules, npm, ESLint |
task-manager/
├── api/
│ ├── lib/
│ │ └── taskStore.js # Shared store: in-memory + optional remote, seed from JSON
│ ├── tasks/
│ │ ├── index.js # GET /api/tasks, POST /api/tasks
│ │ └── [id].js # PATCH/DELETE /api/tasks/:id
│ └── tasks.data.json # Seed data (loaded on cold start)
├── public/ # Static assets (e.g. favicon)
├── src/
│ ├── App.jsx # Root layout, toast container, education text, Form + Items
│ ├── Form.jsx # Task creation form (useCreateTask)
│ ├── Items.jsx # Task list (useFetchTasks), loading/error states
│ ├── SingleItem.jsx # One task: checkbox + title + delete (useEditTask, useDeleteTask)
│ ├── reactQueryCustomHooks.jsx # useFetchTasks, useCreateTask, useEditTask, useDeleteTask
│ ├── localStorageUtils.js # readTasksFromStorage, writeTasksToStorage, removeTasksFromStorage
│ ├── utils.js # Axios instance (base URL from VITE_API_BASE_URL or /api/tasks)
│ ├── index.css # Global and component styles
│ └── main.jsx # React root, QueryClientProvider, global CSS
├── index.html # Entry HTML, meta tags, React mount
├── vercel.json # Build command, output dir, SPA rewrites
├── package.json
└── README.md-
Install dependencies
cd task-manager npm install -
Start the development server
npm run dev
Open
http://localhost:5173. The app uses/api/tasksby default (see Environment Variables). Withnpx vercel devyou can run the serverless API locally. -
Optional: run serverless API locally
npx vercel dev
This serves both the Vite app and the
api/routes so the frontend talks to the same endpoints as in production.
The app works without any .env file. Use environment variables when you need to change the API base URL or (on the server) point to a remote task API.
| Variable | Default | Purpose |
|---|---|---|
VITE_API_BASE_URL |
/api/tasks |
Base URL for all API requests. Set to /api/tasks when deploying to Vercel so the frontend uses the colocated serverless API. Use http://localhost:5000/api/tasks when using the Express reference backend. |
Creating .env or .env.local (frontend)
Create a file in task-manager/ (e.g. .env.local) and restart the dev server:
# Use colocated serverless API (default for Vercel)
VITE_API_BASE_URL=/api/tasks
# Or point to Express reference backend when running locally
# VITE_API_BASE_URL=http://localhost:5000/api/tasksVite only exposes variables prefixed with VITE_ to the client. Do not put secrets here.
| Variable | Default | Purpose |
|---|---|---|
REMOTE_TASKS_API |
(none) | If set, the serverless store proxies all reads/writes to this URL instead of using in-memory + seed. Useful for a shared backend. |
To set env vars in Vercel: Project → Settings → Environment Variables. Add VITE_API_BASE_URL=/api/tasks for the frontend; add REMOTE_TASKS_API only if you use a remote task API.
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (default port 5173). |
npm run build |
Production build; output in dist/. |
npm run preview |
Serve the production build locally. |
npm run lint |
Run ESLint on src (.js, .jsx). |
npm run lint:fix |
Run ESLint with auto-fix. |
Serverless routes in api/ are deployed automatically by Vercel; no extra script is required.
The frontend expects these REST endpoints (implemented by the serverless handlers in api/tasks/ or by the Express reference backend).
Returns all tasks.
Response
{
"taskList": [{ "id": "abc123", "title": "walk the dog", "isDone": false }]
}Creates a task. Body must include title.
Request
POST /api/tasks
Content-Type: application/json
{ "title": "Ship serverless" }Response
{
"task": { "id": "xyz", "title": "Ship serverless", "isDone": false }
}Updates the task’s isDone flag.
Request
PATCH /api/tasks/xyz
Content-Type: application/json
{ "isDone": true }Response
{ "msg": "task updated" }Deletes the task.
Request
DELETE /api/tasks/xyzResponse
{ "msg": "task removed" }main.jsx– Renders the app into#root, wraps it withQueryClientProvider, and imports global CSS and React Toastify styles. Query client is configured with long-lived cache and no automatic refetch on focus/mount.App.jsx– Root layout:ToastContainer(bottom-right), short education text,Form, andItems. No routing; single-page layout.Form.jsx– Controlled input and “Add Task” button. UsesuseCreateTask(); on submit it calls the mutation and clears the input on success. Button is disabled whileisLoading.Items.jsx– UsesuseFetchTasks()to get{ isLoading, isError, data }. Renders loading/error/empty states or mapsdata.taskListtoSingleItemcomponents. Validates thatdata.taskListis an array.SingleItem.jsx– One task: checkbox (togglesisDoneviauseEditTask), title (strikethrough when done), delete button (usesuseDeleteTask, disabled whiledeleteTaskLoading). Expectsitemwithid,title,isDone.reactQueryCustomHooks.jsx– DefinesuseFetchTasks,useCreateTask,useEditTask,useDeleteTask. All usecustomFetchfromutils.js. Fetch usesinitialDatafrom localStorage and syncs API result back to localStorage. Mutations update the React Query cache optimistically and sync to localStorage; toasts on success/error.localStorageUtils.js– Keyreact-query-task-manager. ExportsreadTasksFromStorage(),writeTasksToStorage(taskList),removeTasksFromStorage(). Safe for non-browser (no-op ifwindow/localStorage missing).utils.js– Builds Axios instance withbaseURL:import.meta.env.VITE_API_BASE_URL || "/api/tasks", trailing slash stripped. All API calls go through this instance.
// Fetch: cache-first, hydrate from localStorage, then sync from API
const { isLoading, data, isError } = useFetchTasks();
// initialData: readTasksFromStorage() → { taskList }
// queryFn: customFetch.get("") → API response
// onSuccess: writeTasksToStorage(result.taskList)
// staleTime: Infinity, cacheTime: 24h
// Create: optimistic update + localStorage
const { createTask, isLoading } = useCreateTask();
// mutationFn: customFetch.post("", { title })
// onSuccess: setQueryData(["tasks"], old => ({ ...old, taskList: [...old.taskList, data.task] }))
// writeTasksToStorage(updatedTaskList), toast.success("task added")Mutations for edit and delete follow the same pattern: update cache and localStorage immediately, show toast; no refetch unless you add one.
- Key:
react-query-task-manager. - On load,
useFetchTaskscan supplyinitialDatafromreadTasksFromStorage(), so the list appears immediately. - After a successful fetch or mutation, the hooks call
writeTasksToStorage(taskList)so the next load has fresh data. - This gives a smooth, “offline-friendly” feel without a separate offline database.
Form.jsx– Reusable for any “add item” flow. SwapuseCreateTaskfor another mutation (e.g. different API or payload) or pass a callback prop.SingleItem.jsx– Expectsitem: { id, title, isDone }. ReplaceuseEditTask/useDeleteTaskwith props or other hooks to reuse in another app (e.g. notes, shopping list).- Hooks –
useFetchTasksand the mutation hooks assume the API returns the shapes above. PointcustomFetch(viaVITE_API_BASE_URL) to any backend that matches this contract to reuse the same UI. localStorageUtils.js– ChangeSTORAGE_KEYand reuse in other React apps that need to persist a list in localStorage.
- Import the repo in Vercel and set Root Directory to
task-manager. - Add environment variable:
VITE_API_BASE_URL=/api/tasks(so the built frontend calls the colocated API). - Deploy. Vercel builds the Vite app and deploys
api/as serverless functions;vercel.jsondefines the build and SPA rewrites.
Run the reference backend (see Task Manager Backend Reference), set VITE_API_BASE_URL=http://localhost:5000/api/tasks, and use the frontend against it. No serverless API is required for local learning.
Task Bud, task manager, React Query, Vite, serverless API, Vercel, React 18, TanStack Query, Axios, localStorage, CRUD, optimistic updates, full-stack React, JavaScript, educational project.
The task-manager/ app (Task Bud) shows how to build a full-stack task list with a React frontend and a colocated Vercel serverless API. It is suitable for learning, teaching, and as a template for similar apps. The Express reference backend in the same repo provides the same API for local experimentation and as a stepping stone to a dedicated server.
- Preserves the original Express implementation before serverless.
- Useful for learning middleware, routing, and persistence in Node.
- Starting point for scaling to a dedicated backend (database, auth, etc.) while keeping the same API contract.
The reference backend is not used in the Vercel deployment; the live app uses the serverless API in task-manager/api/.
task-manager-backend-reference/
├── server.js # In-memory task list (npm start)
├── localDataServer.js # File-backed persistence via tasks.json
├── tasks.json # Seed data
├── package.json # Express, cors, morgan, nanoid, nodemon
└── README.md- Express.js – Routes and middleware.
- cors – Allow the React app to call the API from another origin (e.g. localhost:5173).
- morgan – HTTP request logging.
- nanoid – Unique task IDs.
- nodemon – Auto-restart on file changes.
cd task-manager-backend-reference
npm install
# In-memory server (default port 5000)
npm start
# File-backed server (reads/writes tasks.json)
npm run local-serverOptional .env: set PORT=5000 (or another port). Frontend: set VITE_API_BASE_URL=http://localhost:5000/api/tasks in task-manager/.env.local and run npm run dev in task-manager/.
Same as the serverless API:
GET /api/tasks→{ taskList }POST /api/tasks→ body{ title }, returns{ task }PATCH /api/tasks/:id→ body{ isDone }, returns{ msg: "task updated" }DELETE /api/tasks/:id→ returns{ msg: "task removed" }
Unmatched routes can return a 404 message (e.g. “Route does not exist”).
| File | Description | Use case |
|---|---|---|
server.js |
In-memory array, seeded on boot | Quick demos |
localDataServer.js |
Reads/writes tasks.json |
Local dev with file persistence |
- Plug these routes into a larger Express app.
- Replace the in-memory array with a database (MongoDB, PostgreSQL, Prisma, etc.).
- Add auth, pagination, or filtering. The frontend only cares about the URL and response shape; switch between serverless and Express by changing
VITE_API_BASE_URL.
This project is licensed under the MIT License. Feel free to use, modify, and distribute the code as per the terms of the license.
This is an open-source project — feel free to use, enhance, and extend it further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://www.arnobmahmud.com.
Enjoy building and learning! 🚀
Thank you! 😊
