How to Structure a Full-Stack Application with Next.js, Node.js, and React Native 🚀
Modern digital products often need more than one client application.
A business might need:
- A web application
- A mobile application
- An admin dashboard
- A backend API
- A database
- Authentication
- File storage
- Notifications
Instead of building each part as a completely separate system, a better approach is often to create a shared backend that can serve multiple clients.
A practical architecture looks like this:
┌───────────────┐
│ Next.js Web │
└───────┬───────┘
│
│
┌───────▼───────┐
│ Node.js API │
└───────┬───────┘
│
┌───────────┼───────────┐
↓ ↓ ↓
Database Redis Storage
↑
│
┌───────┴───────┐
│ React Native │
│ Mobile App │
└───────────────┘
Both web and mobile applications communicate with the same backend.
Why Separate the Frontend and Backend?
One of the main advantages is separation of responsibilities.
The frontend handles:
- UI
- User interactions
- Navigation
- Client-side state
- User experience
The backend handles:
- Business logic
- Authentication
- Authorization
- Database operations
- Payments
- Validation
- External services
The database stores persistent data.
This creates a clean architecture:
Frontend
↓
API
↓
Business Logic
↓
Database
Next.js for the Web
Next.js provides a strong foundation for the web application.
A project might contain:
app/
├── dashboard/
├── products/
├── orders/
├── profile/
└── settings/
The web application communicates with the backend through HTTP requests.
For example:
GET /api/products
GET /api/orders
POST /api/orders
The frontend shouldn't contain critical business logic that must be trusted.
The backend should validate important operations.
React Native for Mobile
The mobile application can use React Native and Expo.
The architecture becomes:
React Native
↓
REST API
↓
Node.js
↓
Database
This allows the mobile application to use the same business logic and data as the web application.
For example, a user can:
Web
↓
Create Order
↓
Backend
↓
Database
Then open the mobile application:
Mobile
↓
Get Orders
↓
Backend
↓
Database
The data remains consistent because both clients use the same backend.
The Backend API
Node.js can act as the central API layer.
A typical backend structure could look like:
src/
├── routes/
├── controllers/
├── services/
├── models/
├── middleware/
├── utils/
└── config/
The flow can be:
Request
↓
Route
↓
Controller
↓
Service
↓
Database
↓
Response
This separation makes the codebase easier to maintain.
Controllers vs Services
Controllers should focus on HTTP-related operations.
For example:
const getProducts = async (req, res) => {
const products = await productService.getProducts();
res.json({
success: true,
data: products
});
};
The service contains the actual business logic:
const getProducts = async () => {
return await Product.find();
};
This makes the architecture easier to test and extend.
Authentication
Authentication should be handled centrally by the backend.
The flow might look like:
Web / Mobile
↓
Login
↓
Node.js API
↓
Authentication
↓
Access Token
↓
Protected APIs
Both clients can use the same authentication system.
For example:
Next.js
↘
Authentication API
↗
React Native
This avoids duplicating authentication logic across platforms.
Authorization
Authentication tells us who the user is.
Authorization determines what the user can do.
For example:
Admin
├── Create Product
├── Update Product
└── Delete Product
Customer
├── View Product
└── Create Order
The backend should enforce these permissions.
The frontend can hide buttons, but the backend must still validate permissions.
Database Architecture
The database is shared between clients.
Depending on the project, this could be:
- PostgreSQL
- MongoDB
- MySQL
A typical e-commerce system might contain:
Users
Products
Orders
Payments
Subscriptions
Reviews
The backend manages access to these resources.
The clients should never connect directly to the production database.
API Response Design
Consistent API responses make frontend and mobile development easier.
For example:
{
"success": true,
"data": {
"id": "123",
"name": "Product"
}
}
And errors:
{
"success": false,
"message": "Product not found"
}
When the response structure is predictable, different clients can handle it consistently.
Handling Errors
Every application needs proper error handling.
For example:
API Request
↓
Success
↓
Display Data
or:
API Request
↓
Error
↓
User-friendly message
↓
Retry
The backend should log technical details while the client receives a safe and understandable response.
Environment Configuration
Different environments should have different configurations.
For example:
Development
↓
Development API
↓
Development Database
Production:
Production
↓
Production API
↓
Production Database
Environment variables can be used for configuration:
API_URL
DATABASE_URL
JWT_SECRET
STORAGE_KEY
Sensitive values should never be committed directly into the source code.
File Uploads
Many applications need users to upload:
- Profile pictures
- Product images
- Documents
- Videos
Instead of storing large files directly in the database, applications often use object storage.
The architecture can look like:
Web / Mobile
↓
Node.js API
↓
Object Storage
↓
File URL
The database can then store the file metadata or URL.
Caching
As traffic grows, repeatedly querying the database can become expensive.
Redis can be introduced as a caching layer:
Client
↓
Node.js
↓
Redis
↓
Database
For frequently accessed data, the backend can return cached results.
This can reduce database load and improve response times.
Mobile and Web Should Share Business Logic
One important principle is:
Don't duplicate business logic unnecessarily.
For example, if the application calculates discounts:
Product Price
↓
Discount Rules
↓
Final Price
That calculation should ideally happen on the backend if it affects important business operations.
Otherwise, the web application and mobile application might calculate different results.
Centralizing critical business logic keeps the system consistent.
Scaling the Architecture
As the application grows, different components can be scaled independently.
For example:
Load Balancer
/ | \
↓ ↓ ↓
API API API
\ | /
\ | /
Redis
↓
Database
The frontend can also be deployed independently from the backend.
This gives the system more flexibility as traffic increases.
CI/CD
A modern development workflow should automate deployment.
A simplified pipeline:
Developer
↓
GitHub
↓
CI/CD
↓
Tests
↓
Build
↓
Deploy
The same principle can be applied to:
- Web applications
- Backend APIs
- Mobile builds
Automation reduces manual errors and makes releases more predictable.
The Complete Architecture
Putting everything together:
USERS
│
┌───────────┴───────────┐
↓ ↓
Next.js React Native
Web App Mobile App
│ │
└───────────┬───────────┘
↓
REST API
↓
Node.js Backend
│
┌─────────────┼─────────────┐
↓ ↓ ↓
PostgreSQL Redis Storage
This architecture is flexible enough for many modern products.
When Should You Use This Architecture?
This approach works especially well for:
- SaaS platforms
- E-commerce applications
- Booking systems
- Marketplaces
- Business management systems
- Social applications
- Mobile + web products
For a very small project, however, this architecture might be more than you need.
Start simple and introduce additional infrastructure when the product actually requires it.
Final Thoughts
A full-stack application is not just a frontend connected to a backend.
It's a complete system where:
Next.js handles the web experience.
React Native handles the mobile experience.
Node.js handles APIs and business logic.
The database stores application data.
Redis and storage provide additional capabilities when needed.
The most important part is keeping responsibilities clear and avoiding unnecessary complexity.
Build the foundation carefully, keep the architecture simple, and scale individual components when the product requires it.
One backend. Multiple clients. One consistent product experience. 🚀
Top comments (0)