A full-stack boilerplate with a React/Vite frontend and a Node/Express + MongoDB (Mongoose) backend.
This README focuses on what technologies are used and how the frontend/backend are structured (architecture), based on the actual code in this repo.
- Runtime / bundler: Vite + TypeScript (ESM)
- UI: React 19
- Routing: React Router v7 (
RouterProvider+createBrowserRouter) - State: Redux Toolkit slices + RTK Query for data fetching
- Styling: Tailwind CSS v4 +
@tailwindcss/vite - Component library: shadcn/ui (New York style) + Radix primitives + Lucide icons
- Forms & validation: React Hook Form + Zod (+ resolvers)
- Charts / tables / animation: Recharts, TanStack Table, Motion
- Realtime (client):
socket.io-clientwith a provider + hooks pattern - Tooling: ESLint (type-aware) + Prettier
- Runtime: Node.js (ESM) + TypeScript
- HTTP server: Express 5
- Database: MongoDB via Mongoose
- Security headers:
helmet - CORS:
corsmiddleware (origin controlled by env var) - Env loading:
dotenv/config - Dev runner:
tsx(including watch mode) - Tooling: ESLint (type-aware) + Prettier
full-stack-starter-code/
backend/
src/
index.ts # Express app bootstrap (middleware + routes + start)
utils.ts # Mongo connection + startServer helper
controllers/ # Route handlers (business logic)
models/ # Mongoose schemas/models
routes/ # Express routers
types/ # Backend TypeScript types (params/bodies/shared shapes)
validators/ # Express request validation middleware
frontend/
src/
main.tsx # React entry + Redux Provider
App.tsx # RouterProvider
router/ # route definitions
ui/ # layout + pages
store/ # Redux store + slices + RTK Query APIs
sockets/ # socket.io client provider + hooks
shadcn/ # shadcn/ui components, utils, registries
styles/ # Tailwind v4 CSS entry
types/ # shared frontend types
consts/ # constants + env accessors
This repo is two separate Node projects (backend/ and frontend/). Install dependencies in each.
cd backend
npm icd frontend
npm iUsed in backend/src/index.ts:
MONGODB_URI(required): Mongo connection stringFRONTEND_URL: allowed CORS origin (example:http://localhost:5173)PORT: server port (defaults to3000)
Used in frontend/src/consts/consts.ts:
VITE_API_URL: backend base URL (example:http://localhost:3000)
Run the backend and frontend in two terminals.
cd backend
npm run devcd frontend
npm run devfrontend/src/main.tsxmounts the app and wraps it with the Redux<Provider store={store} />.frontend/src/App.tsxrenders React Router’s<RouterProvider />.frontend/src/router/index.tsdefines routes usingcreateBrowserRouter:/→Rootlayout →Homepage*→NotFound
frontend/src/ui/Root.tsxis the layout wrapper. It currently wraps the app inSocketProviderand renders an<Outlet />for nested routes.
frontend/src/store/index.tsconfigures the Redux store:- classic slices (example:
user,counter) - RTK Query API slice(s) (example:
pokemonApi)
- classic slices (example:
frontend/src/store/hooks/index.tsexports typed hooks:useAppDispatchuseAppSelector
frontend/src/store/apis/pokemon.api.tsis a working RTK Query example hitting the public PokeAPI.
How to think about it:
- Slices hold local UI/app state.
- RTK Query holds server/cache state and generates hooks (e.g.
useLazyGetPokemonByNameQuery).
- Tailwind CSS v4 is enabled via
@tailwindcss/vite(seefrontend/vite.config.ts). - The Tailwind entry file is
frontend/src/styles/index.css. - shadcn/ui is configured in
frontend/components.jsonand components live underfrontend/src/shadcn/components/ui/.
@/resolves tofrontend/src(configured infrontend/vite.config.tsandfrontend/tsconfig*.json).
The repo includes a reusable Socket.IO client wrapper:
- Provider:
frontend/src/sockets/SocketProvider.tsx- accepts an array of URLs
- creates and manages one socket connection per URL
- exposes connection status per URL
- Hooks:
frontend/src/sockets/useSockets.tsfrontend/src/sockets/useSocketStatuses.ts
To enable sockets, add URLs in frontend/src/ui/Root.tsx (currently it’s an empty array).
Note: there is no Socket.IO server implemented in the backend in this repo.
backend/src/index.ts:
- Creates an Express app
- Adds middleware:
helmet()express.json()express.urlencoded({ extended: true })cors({ origin: process.env.FRONTEND_URL, credentials: true })
- Registers routers:
/users→usersRouter/posts→postsRouter
- Adds fallback handlers:
- 404 handler for unknown routes
- last-resort error handler (500)
- Reads config from env:
PORT(default 3000)MONGODB_URI(required)
- Connects to MongoDB and starts the server
backend/src/utils.ts:
connectToMongoDB(uri)usesmongoose.connect(...)startServer(app, port)starts the HTTP server and returns theServerinstance
The backend keeps shared TypeScript shapes under backend/src/types/ (examples: ObjectIdParams, CreateUserBody, CreatePostBody). These are used in:
- Controllers: casting
req.params/req.bodyto known shapes - Validators: casting
req.params/req.bodyfor field checks
Note: these types are compile-time only (they don’t change runtime validation/behavior).
backend/src/models/User.model.tsemail(unique),name,posts: ObjectId[]referencing Post
backend/src/models/Post.model.tscreatedBy: ObjectIdreferencing User,title,content
Relationship:
- A
Userowns manyPosts (viaUser.posts[]) - A
Postbelongs to aUser(viaPost.createdBy)
- Returns
{ ok: true }
- GET
/users- 200: list all users
- 500: server error
- GET
/users/:id- 200: user
- 400: invalid ObjectId (
{ message: "invalid user id" }) - 404: not found
- 500: server error
- POST
/users- Body:
{ email: string, name: string } - 201: created user
- 400: missing/invalid fields
emailandnameare requiredemailandnamemust be stringsemailandnamecannot be empty (whitespace-only is rejected)
- 409: duplicate email
- 500: server error
- Body:
- GET
/posts- 200: list posts (sorted newest first),
createdBypopulated with useremail+name - 500: server error
- 200: list posts (sorted newest first),
- GET
/posts/:id- 200: post (with populated
createdBy) - 400: invalid ObjectId (
{ message: "invalid id" }) - 404: not found
- 500: server error
- 200: post (with populated
- POST
/posts- Body:
{ createdBy: string, title: string, content: string } - 201: created post
- 400: missing fields / invalid
createdBycreatedBy,title,contentare requiredcreatedBymust be a valid ObjectId ({ message: "createdBy is not a valid ObjectId" })
- 404: user not found
- 500: server error
- Side effect: pushes the post id into the owning user’s
posts[]
- Body:
- PATCH
/posts/:id- Body:
{ title?: string, content?: string }(at least one required) - 200: updated post
- 400: invalid ObjectId / no fields provided (
{ message: "provide title and/or content" }) - 404: not found
- 500: server error
- Body:
- DELETE
/posts/:id- 204: deleted
- 400: invalid ObjectId (
{ message: "invalid id" }) - 404: not found
- 500: server error
- Side effect: pulls the post id from the owning user’s
posts[]
npm run dev: run with watch (tsx watch src/index.ts)npm run start: run once (tsx src/index.ts)npm run build: compile TS (tsc)npm run typecheck: typecheck only (tsc --noEmit)npm run lint: run ESLintnpm run lint:fix: run ESLint with auto-fix
npm run dev: start Vite dev servernpm run build: typecheck/buildnpm run preview: preview the production buildnpm run check: typecheck + lint + format check