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

Prerequisites

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: