Wasp is a full-stack web framework that uses a simple DSL to describe your app, then generates a React frontend, Node.js backend, and Prisma database layer. It eliminates boilerplate while keeping full flexibility. This guide covers building and deploying Wasp applications.
Installation
# Install Wasp
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
# Verify
wasp version
# Create new project
wasp new my-app
cd my-app
wasp start
The Wasp DSL
// main.wasp
app MyApp {
wasp: { version: "^0.14.0" },
title: "My App",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
google: {},
},
onAuthFailedRedirectTo: "/login",
},
db: {
system: PostgreSQL,
},
}
// Entities
entity User {=psl
id Int @id @default(autoincrement())
tasks Task[]
psl=}
entity Task {=psl
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
userId Int
user User @relation(fields: [userId], references: [id])
psl=}
// Pages and routes
route RootRoute { path: "/", to: MainPage }
page MainPage {
authRequired: true,
component: import { MainPage } from "@src/pages/MainPage",
}
// Operations (queries and actions)
query getTasks {
fn: import { getTasks } from "@src/queries",
entities: [Task],
}
action createTask {
fn: import { createTask } from "@src/actions",
entities: [Task],
}
Server Operations
// src/queries.ts
import { getTasks } from "wasp/server/operations";
export const getTasks = async (args, context) => {
if (!context.user) throw new Error("Not authenticated");
return context.entities.Task.findMany({
where: { userId: context.user.id },
orderBy: { id: "desc" },
});
};
// src/actions.ts
export const createTask = async ({ description }, context) => {
if (!context.user) throw new Error("Not authenticated");
return context.entities.Task.create({
data: {
description,
userId: context.user.id,
},
});
};
React Frontend
// src/pages/MainPage.tsx
import { useQuery } from "wasp/client/operations";
import { getTasks, createTask } from "wasp/client/operations";
import { useState } from "react";
export function MainPage() {
const { data: tasks, isLoading } = useQuery(getTasks);
const [description, setDescription] = useState("");
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await createTask({ description });
setDescription("");
};
if (isLoading) return <div>Loading...</div>;
return (
<div>
<h1>Tasks</h1>
<form
<input value={description} => setDescription(e.target.value)} />
<button type="submit">Add Task</button>
</form>
{tasks?.map((task) => (
<div key={task.id}>{task.description}</div>
))}
</div>
);
}
Building for Production
# Build the project
wasp build
# Output at .wasp/build/
# Contains Dockerfile for both client and server
Deploying to VPS
# Option 1: Docker deployment
cd .wasp/build
docker build -f Dockerfile.server -t myapp-server .
docker build -f Dockerfile.client -t myapp-client .
# docker-compose.yml
services:
server:
image: myapp-server
ports:
- "3001:3001"
environment:
DATABASE_URL: postgres://user:pass@db:5432/myapp
JWT_SECRET: your-jwt-secret
WASP_WEB_CLIENT_URL: https://myapp.example.com
depends_on:
- db
client:
image: myapp-client
ports:
- "3000:80"
environment:
REACT_APP_API_URL: https://api.myapp.example.com
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_DB: myapp
POSTGRES_PASSWORD: password
volumes:
pgdata:
# Option 2: wasp deploy (to Fly.io)
wasp deploy fly launch my-app mia
Summary
Wasp dramatically reduces full-stack boilerplate by generating React, Node.js, and Prisma code from a simple DSL. Authentication, CRUD operations, and client-server communication are built in with type safety. The learning curve is minimal — write operations in plain TypeScript, connect them in the DSL, and Wasp handles the rest. For rapid prototyping and small-to-medium applications, Wasp is one of the fastest paths from idea to deployed product.