Back to Project Setup Guides

Full Stack Application Setup Guide

A comprehensive guide to setting up a full stack application with Next.js frontend and Node.js backend, including project structure, API integration, and more.

1

Project Structure

Create a monorepo structure for your full stack application:

my-fullstack-app/
├── client/           # Frontend (Next.js or React)
│   ├── public/
│   ├── src/
│   ├── package.json
│   └── ...
├── server/           # Backend (Node.js/Express)
│   ├── src/
│   ├── package.json
│   └── ...
├── package.json      # Root package.json
└── README.md
2

Initialize Root Project

Create the root directory and initialize a package.json file.

mkdir my-fullstack-app
cd my-fullstack-app
npm init -y
3

Set Up Frontend (Next.js)

Create a Next.js application in the client directory.

# From the root directory
npx create-next-app client

# Answer the prompts:
# ✔ Would you like to use TypeScript? Yes
# ✔ Would you like to use ESLint? Yes
# ✔ Would you like to use Tailwind CSS? Yes
# ✔ Would you like to use `src/` directory? Yes
# ✔ Would you like to use App Router? Yes
# ✔ Would you like to customize the default import alias? No
4

Set Up Backend (Node.js)

Create a Node.js backend application in the server directory with TypeScript support.

# From the root directory
mkdir server
cd server
npm init -y
npm install dotenv cors
npm install --save-dev nodemon typescript ts-node @types/node @types/cors
5

Configure TypeScript for Backend

Create a tsconfig.json file in the server directory:

// server/tsconfig.json
{
  "compilerOptions": {
    "target": "es2018",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
6

Create Backend Structure

Set up the basic structure for your Express backend:

mkdir -p server/src/config server/src/controllers server/src/models server/src/routes server/src/middleware
7

Create Backend Entry Point

Create the main server file using native Node.js HTTP:

// server/src/index.ts
import http from 'http';
import { URL } from 'url';
import dotenv from 'dotenv';

// Load environment variables
dotenv.config();

// Create server configuration
const PORT = process.env.PORT || 5000;

// Create a simple HTTP server
const server = http.createServer((req, res) => {
  // Parse the request URL
  const parsedUrl = new URL(req.url || '/', `http://${req.headers.host}`);
  
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  // Handle OPTIONS method for CORS preflight
  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  // Basic routing
  if (parsedUrl.pathname === '/') {
    // Home route
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'API is running...' }));
  } else if (parsedUrl.pathname === '/api/users' && req.method === 'GET') {
    // Sample users endpoint
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify([
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]));
  } else {
    // Not found
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

// Start the server
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
8

Create Environment Variables for Backend

Create a .env file in the server directory:

# server/.env
PORT=5000
MONGODB_URI=mongodb://localhost:27017/fullstack-app
JWT_SECRET=your_jwt_secret
9

Update Backend Package.json Scripts

Add scripts to the server's package.json:

// server/package.json
{
  "scripts": {
    "start": "node dist/index.js",
    "dev": "nodemon --exec ts-node src/index.ts",
    "build": "tsc"
  }
}
10

Create API Endpoints

Create a sample API endpoint with controllers and routes:

// server/src/models/User.ts
export interface IUser {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// In-memory database for demonstration
const users: IUser[] = [];

export default {
  findAll: (): Omit<IUser, 'password'>[] => {
    return users.map(({ password, ...user }) => user);
  },
  findById: (id: number): Omit<IUser, 'password'> | undefined => {
    const user = users.find(u => u.id === id);
    if (!user) return undefined;
    const { password, ...userWithoutPassword } = user;
    return userWithoutPassword;
  },
  create: (userData: Omit<IUser, 'id' | 'createdAt'>): Omit<IUser, 'password'> => {
    const newUser: IUser = {
      id: users.length + 1,
      ...userData,
      createdAt: new Date()
    };
    users.push(newUser);
    const { password, ...userWithoutPassword } = newUser;
    return userWithoutPassword;
  }
};

// server/src/controllers/userController.ts
import { IncomingMessage, ServerResponse } from 'http';
import User, { IUser } from '../models/User';

export const getUsers = (req: IncomingMessage, res: ServerResponse) => {
  try {
    const users = User.findAll();
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(users));
  } catch (error: any) {
    res.writeHead(500, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: error.message }));
  }
};

export const createUser = (req: IncomingMessage, res: ServerResponse) => {
  let body = '';
  
  req.on('data', (chunk) => {
    body += chunk.toString();
  });
  
  req.on('end', () => {
    try {
      const userData = JSON.parse(body) as { name: string; email: string; password: string };
      
      if (!userData.name || !userData.email || !userData.password) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: 'Name, email, and password are required' }));
        return;
      }
      
      const newUser = User.create(userData);
      
      res.writeHead(201, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ message: 'User created successfully', user: newUser }));
    } catch (error: any) {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ message: error.message }));
    }
  });
};

// server/src/routes/userRoutes.ts
import { IncomingMessage, ServerResponse } from 'http';
import { getUsers, createUser } from '../controllers/userController';

export default function handleUserRoutes(req: IncomingMessage, res: ServerResponse) {
  if (req.method === 'GET') {
    getUsers(req, res);
  } else if (req.method === 'POST') {
    createUser(req, res);
  } else {
    res.writeHead(405, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'Method not allowed' }));
  }
}

// Update server/src/index.ts to include the routes
import http from 'http';
import { URL } from 'url';
import dotenv from 'dotenv';
import handleUserRoutes from './routes/userRoutes';

// Load environment variables
dotenv.config();

// Create server configuration
const PORT = process.env.PORT || 5000;

// Create a simple HTTP server
const server = http.createServer((req, res) => {
  // Parse the request URL
  const parsedUrl = new URL(req.url || '/', `http://${req.headers.host}`);
  
  // Set CORS headers
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE');
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
  
  // Handle OPTIONS method for CORS preflight
  if (req.method === 'OPTIONS') {
    res.writeHead(204);
    res.end();
    return;
  }

  // Basic routing
  if (parsedUrl.pathname === '/') {
    // Home route
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'API is running...' }));
  } else if (parsedUrl.pathname === '/api/users') {
    handleUserRoutes(req, res);
  } else {
    // Not found
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

// Start the server
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
11

Configure Frontend to Connect to Backend

Create an API service in the frontend to connect to the backend:

// client/src/services/api.ts
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000/api';

export const fetchUsers = async () => {
  try {
    const response = await fetch(`${API_URL}/users`);
    if (!response.ok) throw new Error('Failed to fetch users');
    return await response.json();
  } catch (error) {
    console.error('Error fetching users:', error);
    throw error;
  }
};

export const createUser = async (userData: { name: string; email: string; password: string }) => {
  try {
    const response = await fetch(`${API_URL}/users`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(userData),
    });
    if (!response.ok) throw new Error('Failed to create user');
    return await response.json();
  } catch (error) {
    console.error('Error creating user:', error);
    throw error;
  }
};
12

Create Environment Variables for Frontend

Create a .env.local file in the client directory:

# client/.env.local
NEXT_PUBLIC_API_URL=http://localhost:5000/api
13

Create a Sample Frontend Page

Create a page to display and create users:

// client/src/app/users/page.tsx
'use client';

import { useState, useEffect } from 'react';
import { fetchUsers, createUser } from '../../services/api';

interface User {
  _id: string;
  name: string;
  email: string;
  createdAt: string;
}

export default function UsersPage() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    password: ''
  });

  useEffect(() => {
    const getUsers = async () => {
      try {
        const data = await fetchUsers();
        setUsers(data);
      } catch (err) {
        setError('Failed to fetch users');
      } finally {
        setLoading(false);
      }
    };

    getUsers();
  }, []);

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData({
      ...formData,
      [e.target.name]: e.target.value
    });
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      await createUser(formData);
      // Refresh users list
      const data = await fetchUsers();
      setUsers(data);
      // Reset form
      setFormData({ name: '', email: '', password: '' });
    } catch (err) {
      setError('Failed to create user');
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-6">Users</h1>
      
      <div className="mb-8">
        <h2 className="text-xl font-semibold mb-4">Add New User</h2>
        <form onSubmit={handleSubmit} className="space-y-4">
          <div>
            <label htmlFor="name" className="block mb-1">Name</label>
            <input
              type="text"
              id="name"
              name="name"
              value={formData.name}
              onChange={handleChange}
              required
              className="w-full p-2 border rounded"
            />
          </div>
          <div>
            <label htmlFor="email" className="block mb-1">Email</label>
            <input
              type="email"
              id="email"
              name="email"
              value={formData.email}
              onChange={handleChange}
              required
              className="w-full p-2 border rounded"
            />
          </div>
          <div>
            <label htmlFor="password" className="block mb-1">Password</label>
            <input
              type="password"
              id="password"
              name="password"
              value={formData.password}
              onChange={handleChange}
              required
              className="w-full p-2 border rounded"
            />
          </div>
          <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
            Add User
          </button>
        </form>
      </div>
      
      <div>
        <h2 className="text-xl font-semibold mb-4">User List</h2>
        {users.length === 0 ? (
          <p>No users found</p>
        ) : (
          <ul className="space-y-2">
            {users.map((user) => (
              <li key={user._id} className="border p-3 rounded">
                <p><strong>Name:</strong> {user.name}</p>
                <p><strong>Email:</strong> {user.email}</p>
                <p><strong>Created:</strong> {new Date(user.createdAt).toLocaleDateString()}</p>
              </li>
            ))}
          </ul>
        )}
      </div>
    </div>
  );
}
14

Set Up Root Package.json Scripts

Update the root package.json to run both frontend and backend concurrently:

npm install --save-dev concurrently

// package.json (root)
{
  "scripts": {
    "client": "cd client && npm run dev",
    "server": "cd server && npm run dev",
    "dev": "concurrently \"npm run server\" \"npm run client\"",
    "build:client": "cd client && npm run build",
    "build:server": "cd server && npm run build",
    "build": "npm run build:server && npm run build:client"
  }
}
15

Run the Full Stack Application

This will start both the frontend and backend servers concurrently.

# From the root directory
npm run dev

Additional Resources

Next.js Setup

Detailed guide for setting up a Next.js project with best practices.

View Next.js Guide

Node.js Setup

Comprehensive guide for setting up a Node.js backend with Express.

View Node.js Guide

Deployment Guide

Learn how to deploy your full stack application to various platforms.

View Deployment Guide

Need Help with Your Full Stack Project?

If you need assistance setting up your full stack application or have questions about implementation, I'm here to help.