Starting a React API-Driven App? 8 Decisions to Make Before Coding
Starting a React project is straightforward.
The harder part often begins after the initial project is created.
For an application that communicates with APIs — such as a dashboard, SaaS frontend, CRM, admin panel, internal tool, or client application — you quickly need to make decisions about:
- routing
- API communication
- server state
- client state
- forms
- validation
- styling
- testing
- project structure
There isn't one combination that every React application should use.
The useful approach is to understand what problem each layer solves and make those decisions deliberately before the codebase becomes difficult to change.
This guide walks through eight areas worth considering when starting an API-driven React application.
1. Start with React + TypeScript + Vite
A practical starting point for a React application is:
React
TypeScript
Vite
Each has a different responsibility.
React
React provides the component-based UI layer of the application.
TypeScript
TypeScript adds static typing to JavaScript. In a growing application, it can make data structures, component props, API responses, and function boundaries easier to understand.
Vite
Vite provides the development server and build tooling around the application.
A minimal project can be created with:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
At this point, the project has its basic foundation.
But an API-driven application usually needs several additional decisions.
2. Decide how routing will work
If your application contains multiple screens, routing becomes an important part of the project structure.
For example:
/dashboard
/users
/users/123
/settings
/profile
A routing solution gives the application a consistent place to define:
- URLs
- navigation
- route parameters
- nested routes
- layouts
- route-level UI
React Router is one option for React applications.
A project might organize routing like:
src/
├── routes/
├── pages/
├── components/
└── app/
The exact structure can vary.
The important point is to avoid spreading navigation logic throughout unrelated components.
For a small application, the structure can remain simple.
As the number of routes grows, clearer boundaries become more valuable.
3. Separate server state from client state
This is one of the most important decisions in an API-driven application.
Not all state is the same.
Consider:
Server state
Examples include:
Users
Products
Orders
Posts
Analytics
Notifications
API responses
This information comes from a server and can become stale.
Client state
Examples include:
Sidebar state
Theme preference
Selected filters
UI preferences
Temporary application state
Client-only workflows
This information exists primarily inside the application.
Treating both categories as the same problem can make state management more complicated than necessary.
A useful conceptual separation is:
Server / API state
↓
TanStack Query
Client / application state
↓
Zustand
This doesn't mean these tools are required.
It means they solve different types of problems.
For example:
React
│
├── TanStack Query
│ └── API/server data
│
└── Zustand
└── Client/application state
The important architectural question is:
Did this data come from the server, or does it exist only inside the client?
That question can help determine where the state belongs.
4. Decide how API communication is organized
An API-driven frontend will eventually make many requests.
You can use the native fetch API:
const response = await fetch("/api/users");
const users = await response.json();
Or an HTTP client such as Axios:
const response = await axios.get("/api/users");
Neither approach needs to be treated as mandatory.
The more important decision is where API communication lives.
Avoid having components throughout the application directly implement every HTTP request.
Instead, introduce a boundary for data access.
For example:
src/
├── services/
│ ├── users.ts
│ ├── orders.ts
│ └── auth.ts
Then UI components can consume data through application-level functions or hooks instead of knowing how every API request is constructed.
As the number of endpoints increases, this separation can make the application easier to maintain.
5. Decide how forms and validation work
Forms are usually simple in small applications.
They become more complicated when the application contains:
- login
- registration
- profile editing
- settings
- filters
- checkout
- multi-step forms
- administrative forms
One practical separation is:
React Hook Form
↓
Form handling
Zod
↓
Schema validation
React Hook Form manages form state and submission.
Zod can define the expected structure and validation rules for the data.
For example:
import { z } from "zod";
const profileSchema = z.object({
name: z.string().min(2),
email: z.email(),
});
The conceptual flow becomes:
User input
↓
React Hook Form
↓
Zod schema
↓
Valid / invalid
This separation can make validation rules easier to locate and reuse.
It also helps avoid scattering validation logic across individual input components.
6. Choose a styling approach early
Styling is another decision that is inexpensive to change at the beginning and potentially expensive to replace later.
Common approaches include:
Plain CSS
CSS Modules
Tailwind CSS
Component libraries
For example, with Tailwind CSS:
<button className="rounded-lg px-4 py-2">
Save
</button>
The goal isn't to find one styling technology that every project should use.
Instead, consider:
- project size
- team preferences
- design-system requirements
- component reuse
- long-term maintenance
Once an application has hundreds of components, changing the styling model can become considerably more work.
That makes the initial choice worth thinking about.
7. Establish testing and code-quality tooling
Testing is often postponed because the first version of an application feels small.
The problem is that testing can become harder to introduce once the application has grown substantially.
A project can establish a basic testing foundation early.
For example:
Vitest
React Testing Library
can provide a starting point for unit and component testing.
Then code-quality tools such as:
ESLint
Prettier
can help keep the codebase consistent.
A basic development workflow can look like:
Write code
↓
Type check
↓
Lint
↓
Test
↓
Build
The goal isn't to create a huge CI/CD setup on day one.
The goal is to make quality checks part of normal development before the project becomes difficult to standardize.
8. Decide the project structure before the application grows
A small React project might begin with:
src/
├── components/
├── pages/
├── hooks/
├── utils/
├── App.tsx
└── main.tsx
That may be enough.
As the application grows, a feature-oriented structure can become useful:
src/
├── features/
│ ├── users/
│ ├── orders/
│ └── dashboard/
│
├── components/
├── hooks/
├── services/
├── utils/
└── app/
The important principle is:
Start with the simplest structure that fits the application.
You don't need an elaborate architecture for a small project.
At the same time, don't wait until hundreds of files exist before thinking about boundaries.
A useful evolution can look like:
Small application
↓
Simple structure
↓
More features
↓
Feature-based structure
↓
Larger application
↓
Stronger boundaries
Architecture should evolve with complexity.
Putting the layers together
For an API-driven React application, one possible starting configuration could be:
React
TypeScript
Vite
React Router
Tailwind CSS
TanStack Query
Zustand
React Hook Form
Zod
Vitest
React Testing Library
ESLint
Prettier
The important part is not the number of dependencies.
Each tool should have a clear responsibility.
┌────────────────────────────────────────┐
│ React + Vite │
├────────────────────────────────────────┤
│ Routing → React Router │
│ Styling → Tailwind CSS │
│ Server state → TanStack Query │
│ Client state → Zustand │
│ Forms → React Hook Form │
│ Validation → Zod │
│ Testing → Vitest / Testing Lib │
│ Quality → ESLint / Prettier │
└────────────────────────────────────────┘
This is only an example configuration.
A smaller application might need significantly fewer tools.
For example, an application may not need:
- a dedicated client-state library
- a large validation layer
- extensive testing from the first day
- a complex architecture
The right question isn't:
How many tools should I install?
It is:
What problems does this application actually need to solve?
A practical decision checklist
Before starting the first feature, ask:
Project foundation
- Am I using JavaScript or TypeScript?
- Which build/development tool am I using?
- Which package manager and Node version does the project require?
Navigation
- Does the application need multiple routes?
- How will routes and layouts be organized?
Data
- Which information comes from an API?
- How should server state be cached and refreshed?
- Where will API requests live?
Client state
- Which state needs to be shared?
- Can React's built-in state handle it?
- Do I actually need a dedicated client-state store?
Forms
- How will form state be managed?
- Where will validation rules live?
- Do I need schema validation?
UI
- Which styling approach fits the project?
- Do I need a component library?
- How will reusable components be organized?
Quality
- How will formatting work?
- How will linting work?
- What should be tested?
- What should pass before a production build?
Making these decisions early doesn't mean locking the architecture forever.
It means creating a clear starting point that can evolve as the application grows.
Final takeaway
A React project is more than:
React + Vite
The real starting point is the set of decisions around it.
For an API-driven application, think in layers:
React
↓
TypeScript + Vite
↓
Routing
↓
API / server state
↓
Client state
↓
Forms + validation
↓
Styling
↓
Testing + quality
↓
Project structure
You don't need the largest stack.
You need a stack where every piece has a clear responsibility.
Start with the architecture you understand, add tools because they solve real problems, and make the important setup decisions before repetitive configuration starts taking time away from building the application.
AI-assisted content disclosure: This article was prepared with the assistance of AI and reviewed for technical accuracy before publication.
Top comments (0)