- Learn
- AI App Builders
- Bolt.new
- Building Full-Stack Apps
Learn to build complete full-stack applications in Bolt.new with frontend, backend logic, database integration, and API routes.
Building Full-Stack Apps
Bolt.new's WebContainer technology runs Node.js in the browser, enabling true full-stack development. You can build frontend interfaces, API routes, and connect to databases—all from a single prompt.
What "Full-Stack" Means in Bolt
In Bolt.new, a full-stack application includes:
- Frontend: React, Vue, or other UI frameworks
- Backend: Server-side code running in WebContainers
- API Routes: HTTP endpoints for data operations
- Data Persistence: Database connections or file-based storage
- Authentication: User login and session management
Framework Options
Next.js (Recommended)
Create a Next.js 14 app with the App Router.
Include:
- Homepage
- About page
- Contact form with API route
Use Tailwind CSS for styling.
Why Next.js works well:
- Built-in API routes
- Server-side rendering
- App Router for modern patterns
- Strong AI training data
Remix
Create a Remix app with a simple blog.
Include:
- List of posts
- Individual post pages
- Form to add new posts with action handlers
Express + React
Create a project with:
- Express backend on port 3001
- React frontend on port 5173
- API routes for a todo list
- Proxy configured for development
Building a Complete Application
Let's build a full-stack note-taking app to understand the patterns.
Step 1: Initial Setup
Create a Next.js 14 app for a note-taking application.
Features:
- List of notes with titles and previews
- Create new notes with title and content
- Edit existing notes
- Delete notes
- Search/filter notes
Tech stack:
- Next.js App Router
- Tailwind CSS
- Server Actions for data mutations
- SQLite database with better-sqlite3
Start with the basic project structure.
Step 2: Database Setup
Bolt will create database code. Verify the setup:
// src/lib/db.js
import Database from 'better-sqlite3'
const db = new Database('notes.db')
// Initialize table
db.exec(`
CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
export default db
Step 3: API Routes
Create endpoints for CRUD operations:
Create API routes for the notes app:
- GET /api/notes - List all notes
- GET /api/notes/[id] - Get single note
- POST /api/notes - Create note
- PUT /api/notes/[id] - Update note
- DELETE /api/notes/[id] - Delete note
Include proper error handling and validation.
Generated API route example:
// app/api/notes/route.js
import db from '@/lib/db'
import { NextResponse } from 'next/server'
export async function GET() {
try {
const notes = db.prepare('SELECT * FROM notes ORDER BY updated_at DESC').all()
return NextResponse.json(notes)
} catch (error) {
return NextResponse.json({ error: 'Failed to fetch notes' }, { status: 500 })
}
}
export async function POST(request) {
try {
const { title, content } = await request.json()
if (!title?.trim()) {
return NextResponse.json({ error: 'Title is required' }, { status: 400 })
}
const result = db.prepare(
'INSERT INTO notes (title, content) VALUES (?, ?)'
).run(title, content || '')
const note = db.prepare('SELECT * FROM notes WHERE id = ?').get(result.lastInsertRowid)
return NextResponse.json(note, { status: 201 })
} catch (error) {
return NextResponse.json({ error: 'Failed to create note' }, { status: 500 })
}
}
Step 4: Frontend Components
Create the UI:
Build the frontend for the notes app:
1. NotesLayout - Sidebar with note list, main area for content
2. NotesList - Displays all notes with title and preview
3. NoteEditor - Form to create/edit notes with title and content
4. SearchBar - Filter notes by title
Use React Query for data fetching and cache management.
Style with Tailwind using a clean, minimal design.
Step 5: Connect Frontend to Backend
Wire up the components to the API:
- NotesList should fetch from GET /api/notes
- Clicking a note loads it in the editor
- Save button calls POST or PUT depending on edit/create
- Delete button calls DELETE with confirmation
- Search filters results client-side
Add loading states and error handling.
Working with External APIs
Connecting to Third-Party Services
Add weather display to the app.
Fetch current weather from OpenWeatherMap API.
Show temperature and conditions in the header.
API key should be stored in environment variables.
Bolt creates:
// app/api/weather/route.js
export async function GET() {
const apiKey = process.env.OPENWEATHER_API_KEY
const city = 'New York'
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`
)
if (!response.ok) {
return NextResponse.json({ error: 'Weather fetch failed' }, { status: 500 })
}
const data = await response.json()
return NextResponse.json({
temp: data.main.temp,
conditions: data.weather[0].description
})
}
Environment Variables
Set up secrets properly:
Create a .env.local file with these variables:
- DATABASE_URL
- OPENWEATHER_API_KEY
Show me how to access them in the API routes.
Important: Don't commit real API keys. Use placeholders:
# .env.local
OPENWEATHER_API_KEY=your_api_key_here
Authentication Patterns
Basic Session Auth
Add user authentication:
- Sign up page with email/password
- Login page
- Session management with cookies
- Protected routes that require login
- Logout functionality
Use bcrypt for password hashing.
Store users in SQLite.
Auth Flow Example
// app/api/auth/login/route.js
import bcrypt from 'bcrypt'
import { cookies } from 'next/headers'
import db from '@/lib/db'
export async function POST(request) {
const { email, password } = await request.json()
const user = db.prepare('SELECT * FROM users WHERE email = ?').get(email)
if (!user || !bcrypt.compareSync(password, user.password)) {
return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 })
}
// Create session
const sessionId = crypto.randomUUID()
db.prepare('INSERT INTO sessions (id, user_id) VALUES (?, ?)').run(sessionId, user.id)
cookies().set('session', sessionId, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 1 week
})
return NextResponse.json({ success: true })
}
Real-Time Features
Server-Sent Events
Add real-time updates to the notes app.
When one user creates or edits a note, other users see it immediately.
Use Server-Sent Events for the real-time connection.
WebSocket Alternative
Set up a WebSocket server for real-time chat.
Include:
- Connection management
- Room-based messaging
- User presence indicators
Error Handling Patterns
API Error Handling
Improve error handling throughout the API:
- Consistent error response format
- Specific error messages
- Appropriate HTTP status codes
- Error logging
Create an error utility function to standardize responses.
Standard error format:
// lib/errors.js
export function apiError(message, status = 500) {
return NextResponse.json(
{ error: message, timestamp: new Date().toISOString() },
{ status }
)
}
// Usage in routes
import { apiError } from '@/lib/errors'
export async function GET() {
try {
// ... operation
} catch (error) {
console.error('Notes fetch error:', error)
return apiError('Failed to fetch notes', 500)
}
}
Frontend Error Handling
Add error boundaries and user-friendly error states:
- Error boundary component for React errors
- Toast notifications for API errors
- Retry buttons for failed requests
- Graceful degradation when offline
Database Patterns
SQLite for Simplicity
SQLite works well in Bolt's WebContainer:
-- Schema for a blog
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
content TEXT,
published BOOLEAN DEFAULT false,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
post_id INTEGER REFERENCES posts(id) ON DELETE CASCADE,
author TEXT NOT NULL,
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
Data Relationships
Add categories to the notes app:
- A note can belong to one category
- Categories have name and color
- Show category badge on note cards
- Filter notes by category
Testing Your Full-Stack App
Manual Testing
Test each feature:
- Create: Add new items
- Read: View lists and details
- Update: Edit existing items
- Delete: Remove items
- Edge cases: Empty states, validation
Using the Terminal
# Test API directly with curl
curl http://localhost:3000/api/notes
# Check database
sqlite3 notes.db "SELECT * FROM notes;"
Common Issues
CORS Errors
I'm getting CORS errors when calling the API from the frontend.
Can you add proper CORS headers?
Database Not Persisting
The database resets when I refresh. Is there a way
to persist data between sessions?
API Not Responding
The API route returns 404. Check that the file path
matches the expected route structure.
Performance Optimization
Caching
Add caching to the notes API:
- Cache note listings for 60 seconds
- Invalidate cache when notes are created/updated/deleted
- Use React Query's stale-while-revalidate pattern on frontend
Loading States
Improve perceived performance:
- Skeleton loaders for the notes list
- Optimistic updates when saving
- Debounced search input
- Lazy loading for long content
Summary
Building full-stack apps in Bolt.new:
- Choose the right framework: Next.js works best for full-stack
- Use SQLite for simple data: Works well in WebContainers
- Create proper API routes: RESTful patterns with error handling
- Connect frontend carefully: React Query or SWR for data fetching
- Handle errors gracefully: Both API and UI error states
- Test thoroughly: Manual testing of all CRUD operations
Next Steps
Your app is built—now let's learn how to deploy it so others can use it.