Bolt - Full-Stack Development
Overview
Estimated time: 40โ50 minutes
Learn to build complete full-stack applications using Bolt.new's AI-powered development environment. This tutorial covers frontend frameworks, backend APIs, database integration, and deployment workflows.
Learning Objectives
- Build full-stack applications from scratch using AI assistance
- Integrate frontend frameworks with backend APIs
- Implement authentication, database operations, and real-time features
- Deploy complete applications with proper DevOps practices
Prerequisites
- Bolt - Introduction
- Basic understanding of web development concepts
- Familiarity with JavaScript/TypeScript
Full-Stack Architecture
Modern Stack Combinations
React + Node.js
- Frontend: React with Vite
- Backend: Express.js API
- Database: PostgreSQL/MongoDB
- Deployment: Vercel + Railway
Vue + Python
- Frontend: Vue 3 + Nuxt
- Backend: FastAPI
- Database: SQLite/PostgreSQL
- Deployment: Netlify + Heroku
Svelte + Go
- Frontend: SvelteKit
- Backend: Gin framework
- Database: PostgreSQL
- Deployment: Vercel + Railway
Next.js Full-Stack
- Framework: Next.js 14+
- API: Next.js API routes
- Database: Prisma + PostgreSQL
- Deployment: Vercel
Building a Complete Application
Project Setup with AI
Prompt: "Create a full-stack task management application with:
- React frontend with TypeScript
- Node.js/Express backend
- PostgreSQL database
- User authentication
- Real-time updates
- Modern UI with Tailwind CSS"
Generated Project Structure
task-manager/
โโโ frontend/
โ โโโ src/
โ โ โโโ components/
โ โ โ โโโ TaskList.tsx
โ โ โ โโโ TaskForm.tsx
โ โ โ โโโ AuthForm.tsx
โ โ โโโ hooks/
โ โ โโโ services/
โ โ โโโ App.tsx
โ โโโ package.json
โ โโโ vite.config.ts
โโโ backend/
โ โโโ src/
โ โ โโโ routes/
โ โ โโโ models/
โ โ โโโ middleware/
โ โ โโโ server.ts
โ โโโ package.json
โ โโโ database/
โโโ docker-compose.yml
โโโ README.md
Frontend Development
React Component Architecture
// AI-generated Task component
import React, { useState, useEffect } from 'react';
import { Task, TaskStatus } from '../types';
import { taskService } from '../services/taskService';
interface TaskListProps {
userId: string;
}
export const TaskList: React.FC = ({ userId }) => {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState('all');
useEffect(() => {
const fetchTasks = async () => {
try {
const userTasks = await taskService.getUserTasks(userId);
setTasks(userTasks);
} catch (error) {
console.error('Failed to fetch tasks:', error);
} finally {
setLoading(false);
}
};
fetchTasks();
}, [userId]);
const filteredTasks = tasks.filter(task =>
filter === 'all' || task.status === filter
);
if (loading) return ;
return (
{filteredTasks.map(task => (
))}
);
};
State Management
// AI-generated Zustand store
import { create } from 'zustand';
import { Task, User } from '../types';
interface AppState {
user: User | null;
tasks: Task[];
loading: boolean;
// Actions
setUser: (user: User | null) => void;
addTask: (task: Task) => void;
updateTask: (id: string, updates: Partial) => void;
removeTask: (id: string) => void;
setLoading: (loading: boolean) => void;
}
export const useAppStore = create((set) => ({
user: null,
tasks: [],
loading: false,
setUser: (user) => set({ user }),
addTask: (task) => set((state) => ({
tasks: [...state.tasks, task]
})),
updateTask: (id, updates) => set((state) => ({
tasks: state.tasks.map(task =>
task.id === id ? { ...task, ...updates } : task
)
})),
removeTask: (id) => set((state) => ({
tasks: state.tasks.filter(task => task.id !== id)
})),
setLoading: (loading) => set({ loading })
}));
Backend Development
Express.js API Structure
// AI-generated Express server
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import { authRouter } from './routes/auth';
import { taskRouter } from './routes/tasks';
import { userRouter } from './routes/users';
import { errorHandler } from './middleware/errorHandler';
import { authMiddleware } from './middleware/auth';
const app = express();
// Middleware
app.use(helmet());
app.use(cors({
origin: process.env.FRONTEND_URL || 'http://localhost:3000',
credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/auth', authRouter);
app.use('/api/tasks', authMiddleware, taskRouter);
app.use('/api/users', authMiddleware, userRouter);
// Error handling
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Database Models with Prisma
// AI-generated Prisma schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
username String @unique
password String
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tasks Task[]
@@map("users")
}
model Task {
id String @id @default(cuid())
title String
description String?
status TaskStatus @default(PENDING)
priority Priority @default(MEDIUM)
dueDate DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
tags Tag[]
@@map("tasks")
}
model Tag {
id String @id @default(cuid())
name String @unique
color String @default("#3B82F6")
tasks Task[]
@@map("tags")
}
enum TaskStatus {
PENDING
IN_PROGRESS
COMPLETED
CANCELLED
}
enum Priority {
LOW
MEDIUM
HIGH
URGENT
}
Authentication Implementation
JWT Authentication
// AI-generated auth service
import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret';
export class AuthService {
static async register(email: string, username: string, password: string) {
const existingUser = await prisma.user.findFirst({
where: { OR: [{ email }, { username }] }
});
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(password, 12);
const user = await prisma.user.create({
data: {
email,
username,
password: hashedPassword
},
select: {
id: true,
email: true,
username: true,
avatar: true,
createdAt: true
}
});
const token = jwt.sign({ userId: user.id }, JWT_SECRET, {
expiresIn: '7d'
});
return { user, token };
}
static async login(email: string, password: string) {
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) {
throw new Error('Invalid credentials');
}
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
throw new Error('Invalid credentials');
}
const token = jwt.sign({ userId: user.id }, JWT_SECRET, {
expiresIn: '7d'
});
const { password: _, ...userWithoutPassword } = user;
return { user: userWithoutPassword, token };
}
static async verifyToken(token: string) {
try {
const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
return decoded.userId;
} catch (error) {
throw new Error('Invalid token');
}
}
}
Real-time Features
WebSocket Integration
// AI-generated WebSocket server
import { Server } from 'socket.io';
import { createServer } from 'http';
import { AuthService } from './services/authService';
const server = createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.FRONTEND_URL || "http://localhost:3000",
methods: ["GET", "POST"]
}
});
// Authentication middleware for socket connections
io.use(async (socket, next) => {
try {
const token = socket.handshake.auth.token;
const userId = await AuthService.verifyToken(token);
socket.userId = userId;
next();
} catch (error) {
next(new Error('Authentication error'));
}
});
io.on('connection', (socket) => {
console.log(`User ${socket.userId} connected`);
// Join user-specific room
socket.join(`user:${socket.userId}`);
// Handle task updates
socket.on('task:update', async (data) => {
try {
const updatedTask = await taskService.updateTask(data.taskId, data.updates);
// Broadcast to all clients of this user
io.to(`user:${socket.userId}`).emit('task:updated', updatedTask);
} catch (error) {
socket.emit('error', { message: error.message });
}
});
socket.on('disconnect', () => {
console.log(`User ${socket.userId} disconnected`);
});
});
server.listen(PORT);
Frontend WebSocket Integration
// AI-generated React hook for WebSocket
import { useEffect, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
import { useAppStore } from '../store/appStore';
export const useWebSocket = (token: string | null) => {
const socketRef = useRef(null);
const { updateTask, addTask } = useAppStore();
useEffect(() => {
if (!token) return;
socketRef.current = io(process.env.REACT_APP_API_URL || 'http://localhost:5000', {
auth: { token }
});
const socket = socketRef.current;
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('task:updated', (task) => {
updateTask(task.id, task);
});
socket.on('task:created', (task) => {
addTask(task);
});
socket.on('error', (error) => {
console.error('Socket error:', error);
});
return () => {
socket.disconnect();
};
}, [token, updateTask, addTask]);
const emitTaskUpdate = (taskId: string, updates: any) => {
socketRef.current?.emit('task:update', { taskId, updates });
};
return { emitTaskUpdate };
};
Database Operations
CRUD Operations
// AI-generated task service
export class TaskService {
static async createTask(userId: string, taskData: CreateTaskData) {
return await prisma.task.create({
data: {
...taskData,
userId
},
include: {
tags: true,
user: {
select: {
id: true,
username: true,
avatar: true
}
}
}
});
}
static async getUserTasks(userId: string, filters: TaskFilters = {}) {
const where: any = { userId };
if (filters.status) {
where.status = filters.status;
}
if (filters.priority) {
where.priority = filters.priority;
}
if (filters.search) {
where.OR = [
{ title: { contains: filters.search, mode: 'insensitive' } },
{ description: { contains: filters.search, mode: 'insensitive' } }
];
}
return await prisma.task.findMany({
where,
include: {
tags: true
},
orderBy: [
{ priority: 'desc' },
{ createdAt: 'desc' }
]
});
}
static async updateTask(taskId: string, updates: Partial) {
return await prisma.task.update({
where: { id: taskId },
data: updates,
include: {
tags: true
}
});
}
static async deleteTask(taskId: string) {
return await prisma.task.delete({
where: { id: taskId }
});
}
}
Testing Strategy
Frontend Testing
// AI-generated React component tests
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { TaskList } from '../components/TaskList';
import { taskService } from '../services/taskService';
jest.mock('../services/taskService');
const mockTaskService = taskService as jest.Mocked;
describe('TaskList', () => {
beforeEach(() => {
mockTaskService.getUserTasks.mockResolvedValue([
{
id: '1',
title: 'Test Task',
status: 'PENDING',
priority: 'HIGH',
createdAt: new Date(),
userId: 'user1'
}
]);
});
it('renders tasks correctly', async () => {
render( );
await waitFor(() => {
expect(screen.getByText('Test Task')).toBeInTheDocument();
});
});
it('filters tasks by status', async () => {
render( );
const filterButton = screen.getByText('Completed');
fireEvent.click(filterButton);
await waitFor(() => {
expect(mockTaskService.getUserTasks).toHaveBeenCalledWith('user1', { status: 'COMPLETED' });
});
});
});
Backend Testing
// AI-generated API tests
import request from 'supertest';
import { app } from '../server';
import { prisma } from '../database/prisma';
describe('Task API', () => {
let authToken: string;
let userId: string;
beforeAll(async () => {
// Create test user and get auth token
const response = await request(app)
.post('/api/auth/register')
.send({
email: '[email protected]',
username: 'testuser',
password: 'password123'
});
authToken = response.body.token;
userId = response.body.user.id;
});
afterAll(async () => {
await prisma.task.deleteMany();
await prisma.user.deleteMany();
});
describe('POST /api/tasks', () => {
it('creates a new task', async () => {
const taskData = {
title: 'Test Task',
description: 'Test Description',
priority: 'HIGH'
};
const response = await request(app)
.post('/api/tasks')
.set('Authorization', `Bearer ${authToken}`)
.send(taskData)
.expect(201);
expect(response.body.title).toBe(taskData.title);
expect(response.body.userId).toBe(userId);
});
});
});
Deployment
Docker Configuration
# AI-generated Dockerfile
FROM node:18-alpine AS base
# Frontend build stage
FROM base AS frontend-build
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci --only=production
COPY frontend/ ./
RUN npm run build
# Backend build stage
FROM base AS backend-build
WORKDIR /app/backend
COPY backend/package*.json ./
RUN npm ci --only=production
COPY backend/ ./
RUN npm run build
# Production stage
FROM base AS production
WORKDIR /app
COPY --from=backend-build /app/backend/dist ./dist
COPY --from=backend-build /app/backend/node_modules ./node_modules
COPY --from=frontend-build /app/frontend/dist ./public
EXPOSE 5000
CMD ["node", "dist/server.js"]
Docker Compose
# AI-generated docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "5000:5000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/taskmanager
- JWT_SECRET=${JWT_SECRET}
depends_on:
- db
volumes:
- ./uploads:/app/uploads
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=taskmanager
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
Performance Optimization
Frontend Optimization
// AI-generated performance optimizations
import { lazy, Suspense, memo } from 'react';
// Code splitting
const TaskDetail = lazy(() => import('./TaskDetail'));
const TaskChart = lazy(() => import('./TaskChart'));
// Memoized components
export const TaskCard = memo(({ task, onUpdate }: TaskCardProps) => {
// Component implementation
});
// Debounced search
import { useDeferredValue, useMemo } from 'react';
export const TaskSearch = () => {
const [searchTerm, setSearchTerm] = useState('');
const deferredSearchTerm = useDeferredValue(searchTerm);
const filteredTasks = useMemo(() =>
tasks.filter(task =>
task.title.toLowerCase().includes(deferredSearchTerm.toLowerCase())
), [tasks, deferredSearchTerm]
);
};
Backend Optimization
// AI-generated caching and optimization
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
export class CacheService {
static async getUserTasks(userId: string) {
const cacheKey = `user:${userId}:tasks`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const tasks = await TaskService.getUserTasks(userId);
await redis.setex(cacheKey, 300, JSON.stringify(tasks)); // 5 min cache
return tasks;
}
static async invalidateUserCache(userId: string) {
await redis.del(`user:${userId}:tasks`);
}
}
Best Practices
Code Organization
- Feature-based folder structure
- Consistent naming conventions
- Separation of concerns
- Reusable components and services
Performance
- Code splitting and lazy loading
- Optimistic UI updates
- Efficient caching strategies
- Database query optimization
Security
- Input validation and sanitization
- Proper authentication flow
- HTTPS and secure headers
- Environment variable management
Testing
- Unit tests for components and services
- Integration tests for API endpoints
- E2E tests for critical user flows
- Automated testing in CI/CD
Next Steps
You've learned to build complete full-stack applications with AI assistance. Continue with:
- Bolt - Cheatsheet โ Quick reference for templates and workflows
- AI Agents - Performance & Optimization โ Advanced optimization techniques
- AI Agents - Security & Privacy โ Security best practices