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.
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
Initialize Root Project
Create the root directory and initialize a package.json file.
mkdir my-fullstack-app
cd my-fullstack-app
npm init -y
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
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
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"]
}
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
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}`);
});
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
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"
}
}
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}`);
});
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;
}
};
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
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>
);
}
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"
}
}
Run the Full Stack Application
This will start both the frontend and backend servers concurrently.
# From the root directory
npm run dev
Additional Resources
Deployment Guide
Learn how to deploy your full stack application to various platforms.
View Deployment GuideNeed 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.