web/docs/general/spec.md
import { CardLink } from "@site/src/components/CardLink";
You define and configure the high level of your app (pages, routes, queries, actions, auth, ...) in a main.wasp.ts file in the root of your project. We call this file the Wasp Spec.
You write the Wasp Spec in TypeScript, so you get out-of-the-box support in all editors, full type checking, and the flexibility of a real programming language while configuring your app.
:::info Coming from an older version of Wasp? The Wasp Spec replaces two older ways of configuring a Wasp app:
main.wasp).main.wasp.ts, with the class-based new App(...) API).If you're upgrading from Wasp 0.23.X to 0.24.X, start with the migration guide. Then pick the conversion guide matching your old config:
import { app, page, route, query } from "@wasp.sh/spec";
import MainPage from "./src/MainPage" with { type: "ref" };
import { getTasks } from "./src/queries" with { type: "ref" };
export default app({
name: "todoApp",
title: "ToDo App",
wasp: { version: "^0.24.0" },
spec: [
route("MainRoute", "/", page(MainPage, { authRequired: true })),
query(getTasks, { entities: ["Task"] }),
],
});
You build your app by:
app, page, route, query, ...) from @wasp.sh/spec.with { type: "ref" }.app({ ... }) with your app's configuration, listing all the pages, routes, queries, actions, etc. in the spec property.spec is short for specification: the pages, routes, queries, actions, APIs, jobs and CRUDs that make up your app.
wasp installThe @wasp.sh/spec package doesn't exist on npm, but it is generated by Wasp per project, so we can customize it to fit the needs of your app. We do this through the wasp install command, which installs your app's dependencies and sets up the generated Wasp Spec package. You'll have to run it at least once after creating a new Wasp project, but you might also need to run it again later on when the generated spec needs to be updated.
If the Spec needs to be regenerated, Wasp will tell you to run wasp install before being able to start the app. Usually, this might happen when upgrading Wasp versions, running wasp clean, or removing the node_modules folder.
Anywhere the Wasp Spec expects your app's function or component (like a page's component or a query's fn), you can provide it in one of two ways:
Recommended
Import the value with the regular syntax, adding with { type: "ref" }. Use it when importing components or functions from src/ so Wasp can connect them to pages, actions, queries, and other specifications.
import MainPage from "./src/MainPage" with { type: "ref" };
import { getTasks } from "./src/queries" with { type: "ref" };
export default app({
spec: [page(MainPage), query(getTasks)],
});
The import paths are relative to the *.wasp.ts file they're written in (see multiple spec files):
import LoginPage from "./LoginPage" with { type: "ref" };
export const auth = [page(LoginPage)];
:::note Limitations
Reference imports have some limitations:
*.wasp.ts files.src directory.export { X } from "./X" with { type: "ref" }). Import it first, then re-export it if needed.import * as something from './src/something' with { type: "ref" }) aren't supported. Use named or default imports instead.The vast majority of Wasp apps won't run into these limitations, so we recommend using reference imports by default.
:::
ref helper {#reference-objects}Use ref(...) when a direct reference import is not practical. Import ref from @wasp.sh/spec, then pass it an import object with import (or importDefault) and from:
import { ref } from "@wasp.sh/spec";
export default app({
spec: [
page(ref({ importDefault: "MainPage", from: "./src/MainPage" })),
query(ref({ import: "getTasks", from: "./src/queries" })),
// You can rename a named import with `alias`:
query(ref({ import: "getTasks", alias: "getAllTasks", from: "./src/queries" })),
],
});
The from path is relative to the *.wasp.ts file where you call ref(...) and must resolve inside your project's src directory.
:::note Limitation
You can't re-export ref from @wasp.sh/spec (export { ref } from "@wasp.sh/spec"). Import it first, then re-export it if needed.
:::
For larger apps you don't have to keep everything in main.wasp.ts. You can move related specifications into their own *.wasp.ts files and combine them in main.wasp.ts. This works well for vertical slices, like keeping a feature's page, route, query, and action specifications in that feature's folder.
Each feature file exports it's own Spec:
import { page, route, type Spec } from "@wasp.sh/spec";
import LoginPage from "./LoginPage" with { type: "ref" };
import SignupPage from "./SignupPage" with { type: "ref" };
export const authSpec: Spec = [
route("SignupRoute", "/signup", page(SignupPage)),
route("LoginRoute", "/login", page(LoginPage)),
];
The Spec annotation gives TypeScript enough information to validate the specification in its own file before it's added to the main.wasp.ts.
Then main.wasp.ts imports it and joins in into the spec:
import { app, page, route } from "@wasp.sh/spec";
import MainPage from "./src/MainPage" with { type: "ref" };
import { authSpec } from "./src/auth/auth.wasp";
export default app({
name: "todoApp",
title: "ToDo App",
wasp: { version: "^0.24.0" },
spec: [
route("MainRoute", "/", page(MainPage, { authRequired: true })),
authSpec,
],
});
All spec files should have the .wasp.ts extension, so they are included in the tsconfig.wasp.json and type-checked.
Wasp sets the NODE_ENV environment variable based on which command you use to run Wasp:
"development" during wasp start (and some other commands that compile the project, like wasp db migrate-dev)."production" during wasp build.Because the Wasp Spec is just TypeScript, you can read this variable to switch config values per environment:
const isProd = process.env.NODE_ENV === "production";
export default app({
//...
emailSender: {
provider: isProd ? "SMTP" : "Dummy",
defaultFrom: { email: "[email protected]" },
},
});