PocketBase: A Complete Backend in a Single Binary
PocketBase is an open-source backend that ships as a single executable. You download one file, run it, and get a SQLite database, user authentication, file storage, real-time subscriptions, and an auto-generated REST API — all with an admin dashboard. No Docker, no database server, no configuration files.
It fills a gap between "I'll just use a JSON file" and "I need to set up PostgreSQL, Redis, an auth provider, and an object store." For prototypes, internal tools, small SaaS products, and side projects, PocketBase eliminates the entire backend setup phase.
Getting Started
Download the binary for your platform from the PocketBase website, then run it:
./pocketbase serve
That's it. The admin UI is at http://127.0.0.1:8090/_/ and the API is live at http://127.0.0.1:8090/api/. On first launch, you'll create an admin account through the UI.
Collections Are Your Database Tables
PocketBase organizes data into collections, which map to SQLite tables. You create them through the admin UI or via the API. There are three types:
- Base collections — standard data tables with custom fields
- Auth collections — like base collections but with built-in email/password authentication, OAuth2, and user management
- View collections — read-only collections backed by SQL queries (like database views)
Each collection automatically gets id, created, and updated fields. You add your own fields through the UI: text, number, bool, email, URL, date, file, relation, select, and JSON.
Once a collection exists, PocketBase generates full CRUD endpoints:
# List records
curl http://127.0.0.1:8090/api/collections/posts/records
# Create a record
curl -X POST http://127.0.0.1:8090/api/collections/posts/records \
-H "Content-Type: application/json" \
-d '{"title": "Hello", "body": "First post", "published": true}'
# Filter records
curl "http://127.0.0.1:8090/api/collections/posts/records?filter=(published=true)&sort=-created"
The filter syntax supports comparison operators, logical operators, and nested field access. It's not SQL — it's a simpler query language that PocketBase translates to SQL internally.
Authentication Out of the Box
Create an auth collection (the default users collection works), and you get registration, login, password reset, email verification, and OAuth2 — all handled by PocketBase:
# Register
curl -X POST http://127.0.0.1:8090/api/collections/users/records \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "securepass123", "passwordConfirm": "securepass123"}'
# Login
curl -X POST http://127.0.0.1:8090/api/collections/users/auth-with-password \
-H "Content-Type: application/json" \
-d '{"identity": "[email protected]", "password": "securepass123"}'
The login response includes a JWT token. Use it in subsequent requests via the Authorization header. OAuth2 providers (Google, GitHub, Discord, and others) are configured through the admin UI with just a client ID and secret.
Access Rules
Every collection has configurable access rules for list, view, create, update, and delete operations. Rules are expressions that evaluate against the current request:
- Empty rule = public access
@request.auth.id != ""= any authenticated user@request.auth.id = user.id= only the record owner@request.auth.role = "admin"= custom role check
This replaces the middleware chain you'd typically write in Express or Fastify. Rules are set per-collection in the admin UI and stored in the database, so they're portable with your data.
File Storage
File fields on collections handle uploads automatically. PocketBase stores files on the local filesystem by default, with S3-compatible storage as an option. Files are served through the API with built-in thumbnail generation for images:
# Upload a file
curl -X POST http://127.0.0.1:8090/api/collections/posts/records \
-F "title=Photo post" \
-F "[email protected]"
# Access the file (with optional thumb parameter)
# http://127.0.0.1:8090/api/files/posts/RECORD_ID/photo.jpg?thumb=100x100
Real-Time Subscriptions
PocketBase supports real-time updates via Server-Sent Events. Subscribe to changes on any collection:
import PocketBase from "pocketbase";
const pb = new PocketBase("http://127.0.0.1:8090");
pb.collection("posts").subscribe("*", (e) => {
console.log(e.action); // 'create', 'update', or 'delete'
console.log(e.record);
});
Subscriptions respect access rules — users only receive events for records they have permission to view.
Extending with Go or JavaScript
PocketBase can be used as a Go framework. Import it as a module and add custom routes, hooks, and middleware:
package main
import (
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/api/custom", func(e *core.RequestEvent) error {
return e.JSON(200, map[string]string{"message": "custom endpoint"})
})
return se.Next()
})
app.Start()
}
For simpler customizations, PocketBase also supports JavaScript hooks (using the Goja runtime) that you can place in a pb_hooks directory — no Go compilation needed.
When PocketBase Fits
PocketBase works well for:
- Prototypes and MVPs — skip the backend setup entirely, validate your idea first
- Internal tools — admin panels, dashboards, data entry apps where you need auth and CRUD
- Side projects — one binary on a $5 VPS handles thousands of concurrent users
- Mobile app backends — the SDKs for JavaScript, Dart, and other languages cover most use cases
- Single-developer projects — no DevOps overhead, just deploy the binary
When to Look Elsewhere
PocketBase is backed by SQLite, which means:
- No horizontal scaling — you can't split the database across multiple servers. One machine handles everything. For most projects this is fine (SQLite handles enormous workloads), but if you genuinely need distributed writes, use PostgreSQL.
- No complex migrations — schema changes happen through the UI or API. There's no migration file workflow like Prisma or Drizzle. For teams that need reviewable schema changes in version control, this can be a limitation.
- Limited query complexity — the filter syntax covers common cases but complex analytical queries need raw SQL access through the Go/JS extension points.
Deployment
Deployment is straightforward — copy the binary and your pb_data directory to a server:
# On your server
./pocketbase serve --http="0.0.0.0:8090"
Put it behind Caddy or nginx for TLS termination. Back up the SQLite database with sqlite3 pb_data/data.db ".backup backup.db" or use Litestream for continuous replication to S3.
For production, run it as a systemd service:
[Unit]
Description=PocketBase
After=network.target
[Service]
Type=simple
ExecStart=/opt/pocketbase/pocketbase serve --http="0.0.0.0:8090"
WorkingDirectory=/opt/pocketbase
Restart=on-failure
[Install]
WantedBy=multi-user.target
The Trade-Off
PocketBase trades flexibility for speed of development. You won't build the next Stripe on it, but you can have a working backend with auth, data, files, and real-time updates running in under a minute. For the vast majority of projects that never need to scale beyond a single server, that trade-off is overwhelmingly worth it.
The source is on GitHub, the community is active, and the project is under steady development. If you've been putting off a project because "setting up the backend" felt like too much work, PocketBase removes that excuse entirely.