DEV Community

Cover image for How to Structure a Full-Stack Application with Next.js, Node.js, and React Native
Umidjon Gafforov
Umidjon Gafforov

Posted on

How to Structure a Full-Stack Application with Next.js, Node.js, and React Native

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    │
                 └───────────────┘
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

The web application communicates with the backend through HTTP requests.

For example:

GET /api/products
GET /api/orders
POST /api/orders
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then open the mobile application:

Mobile
 ↓
Get Orders
 ↓
Backend
 ↓
Database
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

The flow can be:

Request
   ↓
Route
   ↓
Controller
   ↓
Service
   ↓
Database
   ↓
Response
Enter fullscreen mode Exit fullscreen mode

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
  });
};
Enter fullscreen mode Exit fullscreen mode

The service contains the actual business logic:

const getProducts = async () => {
  return await Product.find();
};
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Both clients can use the same authentication system.

For example:

Next.js
   ↘
    Authentication API
   ↗
React Native
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

And errors:

{
  "success": false,
  "message": "Product not found"
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

or:

API Request
    ↓
Error
    ↓
User-friendly message
    ↓
Retry
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Production:

Production
    ↓
Production API
    ↓
Production Database
Enter fullscreen mode Exit fullscreen mode

Environment variables can be used for configuration:

API_URL
DATABASE_URL
JWT_SECRET
STORAGE_KEY
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)