# Wasp 0.21 Full Documentation
> This is the full documentation for the latest version of Wasp.
> For other versions, see the links below.
## Full Documentation by Version
- [0.20](https://wasp.sh/llms-full-0.20.txt)
- [0.19](https://wasp.sh/llms-full-0.19.txt)
- [0.18](https://wasp.sh/llms-full-0.18.txt)
- [0.17](https://wasp.sh/llms-full-0.17.txt)
- [0.16](https://wasp.sh/llms-full-0.16.txt)
- [0.15](https://wasp.sh/llms-full-0.15.txt)
- [0.14](https://wasp.sh/llms-full-0.14.txt)
- [0.13](https://wasp.sh/llms-full-0.13.txt)
- [0.12](https://wasp.sh/llms-full-0.12.txt)
- [0.11.8](https://wasp.sh/llms-full-0.11.8.txt)
---
# Getting Started
## Introduction
:::note
If you are looking for the installation instructions, check out the [Quick Start](./quick-start.md) section.
:::
We will give a brief overview of what Wasp is, how it works on a high level and when to use it.
### Wasp is a tool to build modern web applications
It is an opinionated way of building **full-stack web applications**. It takes care of all three
major parts of a web application: **client** (front-end), **server** (back-end) and **database**.
#### Works well with your existing stack
Wasp is not trying to do everything at once but rather focuses on the complexity that arises from connecting all the parts of the stack (client, server, database, deployment).
Wasp is using **React**, **Node.js** and **Prisma** under the hood and relies on them to define web components and server queries and actions.
#### Wasp's secret sauce
At the core is the Wasp compiler which takes the Wasp config and your Javascript code and outputs the client app, server app and deployment code.
The cool thing about having a compiler that understands your code is that it can do a lot of things for you.
Define your app in the Wasp config and get:
- login and signup with Auth UI components,
- full-stack type safety,
- e-mail sending,
- async processing jobs,
- React Query powered data fetching,
- security best practices,
- and more.
You don't need to write any code for these features, Wasp will take care of it for you đ¤¯ And what's even better, Wasp also maintains the code for you, so you don't have to worry about keeping up with the latest security best practices. As Wasp updates, so does your app.
### So what does the code look like?
Let's say you want to build a web app that allows users to **create and share their favorite recipes**.
Let's start with the `main.wasp` file: it is the central file of your app, where you describe the app from the high level.
Let's give our app a title and let's immediately turn on the full-stack authentication via username and password:
```wasp title="main.wasp"
app RecipeApp {
title: "My Recipes",
wasp: { version: "{latestWaspVersion}" },
auth: {
methods: { usernameAndPassword: {} },
onAuthFailedRedirectTo: "/login",
userEntity: User
}
}
```
Let's then add the data models for your recipes. Wasp understands and uses the models from the `schema.prisma` file. We will want to have Users and Users can own Recipes:
```prisma title="schema.prisma"
...
// Data models are defined using Prisma Schema Language.
model User {
id Int @id @default(autoincrement())
recipes Recipe[]
}
model Recipe {
id Int @id @default(autoincrement())
title String
description String?
userId Int
user User @relation(fields: [userId], references: [id])
}
```
Next, let's define how to do something with these data models!
We do that by defining Operations, in this case, a Query `getRecipes` and Action `addRecipe`,
which are in their essence Node.js functions that execute on the server and can, thanks to Wasp, very easily be called from the client.
First, we define these Operations in our `main.wasp` file, so Wasp knows about them and can "beef them up":
```wasp title="main.wasp"
// Queries have automatic cache invalidation and are type-safe.
query getRecipes {
fn: import { getRecipes } from "@src/recipe/operations",
entities: [Recipe],
}
// Actions are type-safe and can be used to perform side-effects.
action addRecipe {
fn: import { addRecipe } from "@src/recipe/operations",
entities: [Recipe],
}
```
... and then implement them in our Javascript (or TypeScript) code (we show just the query here, using TypeScript):
```ts title="src/recipe/operations.ts"
// Wasp generates the types for you.
export const getRecipes: GetRecipes<{}, Recipe[]> = async (_args, context) => {
return context.entities.Recipe.findMany( // Prisma query
{ where: { user: { id: context.user.id } } }
);
};
export const addRecipe ...
```
Now we can very easily use these in our React components!
For the end, let's create a home page of our app.
First, we define it in `main.wasp`:
```wasp title="main.wasp"
...
route HomeRoute { path: "/", to: HomePage }
page HomePage {
component: import { HomePage } from "@src/pages/HomePage",
authRequired: true // Will send user to /login if not authenticated.
}
```
and then implement it as a React component in JS/TS (that calls the Operations we previously defined):
```tsx title="src/pages/HomePage.tsx"
export function HomePage({ user }: { user: User }) {
// Due to full-stack type safety, `recipes` will be of type `Recipe[]` here.
const { data: recipes, isLoading } = useQuery(getRecipes) // Calling our query here!
if (isLoading) {
return
Loading...
}
return (
Recipes
{recipes
? recipes.map((recipe) => (
{recipe.title}
{recipe.description}
))
: 'No recipes defined yet!'}
)
}
```
And voila! We are listing all the recipes in our app đ
This was just a quick example to give you a taste of what Wasp is. For step by step tour through the most important Wasp features, check out the [Todo app tutorial](../tutorial/01-create.md).
:::note
Above we skipped defining `/login` and `/signup` pages to keep the example a bit shorter, but those are very simple to do by using Wasp's Auth UI feature.
:::
### When to use Wasp
Wasp addresses the same core problems that typical web app frameworks are addressing, and it in big part [looks, swims and quacks](https://en.wikipedia.org/wiki/Duck_test) like a web app framework.
#### Best used for
- building full-stack web apps (like e.g. Airbnb or Asana)
- quickly starting a web app with industry best practices
- to be used alongside modern web dev stack (React and Node.js are currently supported)
#### Avoid using Wasp for
- building static/presentational websites
- to be used as a no-code solution
- to be a solve-it-all tool in a single language
### Wasp is a DSL
:::note
You don't need to know what a DSL is to use Wasp, but if you are curious, you can read more about it below.
:::
Wasp does not match typical expectations of a web app framework: it is not a set of libraries, it is instead a simple programming language that understands your code and can do a lot of things for you.
Wasp is a programming language, but a specific kind: it is specialized for a single purpose: **building modern web applications**. We call such languages _DSL_s (Domain Specific Language).
Other examples of _DSL_s that are often used today are e.g. _SQL_ for databases and _HTML_ for web page layouts.
The main advantage and reason why _DSL_s exist is that they need to do only one task (e.g. database queries)
so they can do it well and provide the best possible experience for the developer.
The same idea stands behind Wasp - a language that will allow developers to **build modern web applications with 10x less code and less stack-specific knowledge**.
## Quick Start
### Installation
Welcome, new Waspeteer đ!
Let's create and run our first Wasp app in 3 short steps:
1. **To install Wasp on Linux / OSX / WSL (Windows), open your terminal and run:**
```shell
npm i -g @wasp.sh/wasp-cli@latest
```
âšī¸ Wasp requires Node.js and npm, which are usually installed together: check below for [more details](#requirements).
2. **Then, create a new app by running:**
```shell
wasp new
```
3. **Finally, run the app:**
```shell
cd
wasp start
```
That's it đ You have successfully created and served a new full-stack web app at [http://localhost:3000](http://localhost:3000) and Wasp is serving both frontend and backend for you.
:::note Something Unclear?
Check [More Details](#more-details) section below if anything went wrong with the installation, or if you have additional questions.
:::
:::tip Want an even faster start?
Try out [Wasp AI](../wasp-ai/creating-new-app.md) đ¤ to generate a new Wasp app in minutes just from a title and short description!
:::
:::tip Having trouble running Wasp?
If you get stuck with a weird error while developing with Wasp, try running `wasp clean` - this is the Wasp equivalent of "turning it off and on again"!
Do however let us know about the issue on our GitHub repo or Discord server.
:::
#### What next?
- [ ] đ **Check out the [Todo App tutorial](../tutorial/01-create.md), which will take you through all the core features of Wasp!** đ
- [ ] [Setup your editor](./editor-setup.md) for working with Wasp.
- [ ] Join us on [Discord](https://discord.gg/rzdnErX)! Any feedback or questions you have, we are there for you.
- [ ] Follow Wasp development by subscribing to our newsletter: https://wasp.sh/#signup . We usually send 1 per month, and [Matija](https://github.com/matijaSos) does his best to unleash his creativity to make them engaging and fun to read :D!
---
### More details
#### Requirements
You must have Node.js (and NPM) installed on your machine and available in `PATH`.
A version of Node.js must be >= 22.12.
If you need it, we recommend using [nvm](https://github.com/nvm-sh/nvm) for managing your Node.js installation version(s).
A quick guide on installing/using nvm
Install nvm via your OS package manager (`apt`, `pacman`, `homebrew`, ...) or via the [nvm](https://github.com/nvm-sh/nvm#install--update-script) install script.
Then, install a version of Node.js that you need:
```shell
nvm install 22
```
Finally, whenever you need to ensure a specific version of Node.js is used, run:
```shell
nvm use 22
```
to set the Node.js version for the current shell session.
You can run
```shell
node -v
```
to check the version of Node.js currently being used in this shell session.
Check NVM repo for more details: https://github.com/nvm-sh/nvm.
#### Installation {#detailed-installation}
Open your terminal and run:
```shell
npm i -g @wasp.sh/wasp-cli@latest
```
:::note Looking for the old installer script?
Check out the **[Legacy installer guide](../guides/legacy/installer.md)** for instructions on how to keep using it or how to migrate to npm-based installation.
:::
With Wasp for Windows, we are almost there: Wasp is successfully compiling and running on Windows but there is a bug or two stopping it from fully working. Check it out [here](https://github.com/wasp-lang/wasp/issues/48) if you are interested in helping.
In the meantime, the best way to start using Wasp on Windows is by using [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). Once you set up Ubuntu on WSL, just follow Linux instructions for installing Wasp. You can refer to this [article](https://wasp.sh/blog/2023/11/21/guide-windows-development-wasp-wsl) if you prefer a step by step guide to using Wasp in WSL environment. If you need further help, reach out to us on [Discord](https://discord.gg/rzdnErX) - we have some community members using WSL that might be able to help you.
:::caution
If you are using WSL2, make sure that your Wasp project is not on the Windows file system, but instead on the Linux file system. Otherwise, Wasp won't be able to detect file changes, due to the [issue in WSL2](https://github.com/microsoft/WSL/issues/4739).
:::
If the other methods are not working for you or your OS is not supported, you can try building Wasp from the source.
To install from source, you need to clone the [wasp repo](https://github.com/wasp-lang/wasp), install [Cabal](https://cabal.readthedocs.io/en/stable/getting-started.html) on your machine and then run `cabal install` from the `waspc/` dir.
If you have never built Wasp before, this might take some time due to `cabal` downloading dependencies for the first time.
Check [waspc/](https://github.com/wasp-lang/wasp/tree/main/waspc) for more details on building Wasp from the source.
## Editor Setup
:::note
This page assumes you have already installed Wasp. If you do not have Wasp installed yet, check out the [Quick Start](./quick-start.md) guide.
:::
Wasp comes with the Wasp language server, which gives supported editors powerful support and integration with the language.
### VSCode
Currently, Wasp only supports integration with VSCode. Install the [Wasp language extension](https://marketplace.visualstudio.com/items?itemName=wasp-lang.wasp) to get syntax highlighting and integration with the Wasp language server.
The extension enables:
- syntax highlighting for `.wasp` files
- the Prisma extension for `.prisma` files
- scaffolding of new project files
- code completion
- diagnostics (errors and warnings)
- go to definition
and more!
------
# Tutorial
## 1. Creating a New Project
:::info
You'll need to have the latest version of Wasp installed locally to follow this tutorial. If you haven't installed it yet, check out the [QuickStart](../quick-start) guide!
:::
In this section, we'll guide you through the process of creating a simple Todo app with Wasp. In the process, we'll take you through the most important and useful features of Wasp.
If you get stuck at any point (or just want to chat), reach out to us on [Discord](https://discord.gg/rzdnErX) and we will help you!
You can find the complete code of the app we're about to build [here](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoApp).
### Creating a Project
To setup a new Wasp project, run the following command in your terminal:
```sh
wasp new TodoApp -t minimal
```
We are using the `minimal` template because we're going to implement the app from scratch, instead of the more full-featured default template.
Enter the newly created directory and start the development server:
```sh
cd TodoApp
wasp start
```
`wasp start` will take a bit of time to start the server the first time you run it in a new project.
You will see log messages from the client, server, and database setting themselves up. When everything is ready, a new tab should open in your browser at `http://localhost:3000` with a simple placeholder page:
Wasp has generated for you the full front-end and back-end code of the app! Next, we'll take a closer look at how the project is structured.
### A note on supported languages
Wasp supports both JavaScript and TypeScript out of the box, but you are free to choose between or mix JavaScript and TypeScript as you see fit.
We'll provide you with both JavaScript and TypeScript code in this tutorial.
Code blocks will have a toggle to switch between vanilla JavaScript and TypeScript.
Try it out:
:::note Welcome to JavaScript!
You are now reading the JavaScript version of the docs. The site will remember your preference as you switch pages.
You'll have a chance to change the language on every code snippet - both the snippets and the text will update accordingly.
:::
:::note Welcome to TypeScript!
You are now reading the TypeScript version of the docs. The site will remember your preference as you switch pages.
You'll have a chance to change the language on every code snippet - both the snippets and the text will update accordingly.
:::
## 2. Project Structure
By default, Wasp will create a TypeScript project. We recommend using TypeScript in new projects, and you can always mix-and-match TypeScript and JavaScript files.
To use JavaScript in the main page, you must manually rename the file
`src/MainPage.tsx` to `src/MainPage.jsx`. Restart `wasp start` after you do this.
No updates to the `main.wasp` file are necessary - it stays the same regardless of the language you use.
After creating a new Wasp project and renaming the `src/MainPage.tsx` file, your project should look like this:
```python
.
âââ .gitignore
âââ main.wasp # Your Wasp code goes here.
âââ schema.prisma # Your database models go here.
âââ package.json # Your dependencies and project info go here.
âââ public # Your static files (e.g., images, favicon) go here.
â  âââ favicon.ico
âââ src # Your source code (JS/React/Node.js) goes here.
â  âââ Main.css
# highlight-next-line
â  âââ MainPage.jsx
â  âââ assets
â  â  âââ logo.svg
â  âââ vite-env.d.ts
âââ tsconfig.json
âââ vite.config.ts
```
After creating a new Wasp project, your project should look like this:
```python
.
âââ .gitignore
âââ main.wasp # Your Wasp code goes here.
âââ schema.prisma # Your database models go here.
âââ package.json # Your dependencies and project info go here.
âââ public # Your static files (e.g., images, favicon) go here.
â  âââ favicon.ico
âââ src # Your source code (TS/React/Node.js) goes here.
â  âââ Main.css
â  âââ MainPage.tsx
â  âââ assets
â  â  âââ logo.svg
â  âââ vite-env.d.ts
âââ tsconfig.json
âââ vite.config.ts
```
By _your code_, we mean the _"the code you write"_, as opposed to the code generated by Wasp. Wasp allows you to organize and structure your code however you think is best - there's no need to separate client files and server files into different directories.
We'd normally recommend organizing code by features (i.e., vertically).
However, since this tutorial contains only a handful of files, there's no need for fancy organization.
We'll keep it simple by placing everything in the root `src` directory.
Many other files (e.g., `tsconfig.json`, `vite-env.d.ts`, `.wasproot`, etc.) help Wasp and the IDE improve your development experience with autocompletion, IntelliSense, and error reporting.
The `vite.config.ts` file is used to configure [Vite](https://vitejs.dev/guide/), Wasp's build tool of choice.
We won't be configuring Vite in this tutorial, so you can safely ignore the file. Still, if you ever end up wanting more control over Vite, you'll find everything you need to know in [custom Vite config docs](../project/custom-vite-config.md).
The `schema.prisma` file is where you define your database schema using [Prisma](https://www.prisma.io/). We'll cover this a bit later in the tutorial.
The most important file in the project is `main.wasp`. Wasp uses the configuration within it to perform its magic. Based on what you write, it generates a bunch of code for your database, server-client communication, React routing, and more.
Let's take a closer look at `main.wasp`
### `main.wasp`
`main.wasp` is your app's definition file.
It defines the app's central components and helps Wasp to do a lot of the legwork for you.
:::tip Wasp TS config \[Early-preview feature\]
If you wish, you can alternatively define your [Wasp config in TS](../general/wasp-ts-config.md) (`main.wasp.ts`) instead of `main.wasp`.
:::
The file is a list of _declarations_. Each declaration defines a part of your app.
The default `main.wasp` file generated with `wasp new` on the previous page looks like this:
```wasp title="main.wasp"
app TodoApp {
wasp: {
version: "{latestWaspVersion}" // Pins the version of Wasp to use.
},
title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted!
head: [
"",
]
}
route RootRoute { path: "/", to: MainPage }
page MainPage {
// We specify that the React implementation of the page is exported from
// `src/MainPage.jsx`. This statement uses standard JS import syntax.
// Use `@src` to reference files inside the `src` folder.
component: import { MainPage } from "@src/MainPage"
}
```
```wasp title="main.wasp"
app TodoApp {
wasp: {
version: "{latestWaspVersion}" // Pins the version of Wasp to use.
},
title: "TodoApp", // Used as the browser tab title. Note that all strings in Wasp are double quoted!
head: [
"",
]
}
route RootRoute { path: "/", to: MainPage }
page MainPage {
// We specify that the React implementation of the page is exported from
// `src/MainPage.tsx`. This statement uses standard JS import syntax.
// Use `@src` to reference files inside the `src` folder.
component: import { MainPage } from "@src/MainPage"
}
```
This file uses three declaration types:
- **app**: Top-level configuration information about your app.
- **route**: Describes which path each page should be accessible from.
- **page**: Defines a web page and the React component that gets rendered when the page is loaded.
In the next section, we'll explore how **route** and **page** work together to build your web app.
## 3. Pages & Routes
In the default `main.wasp` file created by `wasp new`, there is a **page** and a **route** declaration:
```wasp title="main.wasp"
route RootRoute { path: "/", to: MainPage }
page MainPage {
// We specify that the React implementation of the page is exported from
// `src/MainPage.jsx`. This statement uses standard JS import syntax.
// Use `@src` to reference files inside the `src` folder.
component: import { MainPage } from "@src/MainPage"
}
```
```wasp title="main.wasp"
route RootRoute { path: "/", to: MainPage }
page MainPage {
// We specify that the React implementation of the page is exported from
// `src/MainPage.tsx`. This statement uses standard JS import syntax.
// Use `@src` to reference files inside the `src` folder.
component: import { MainPage } from "@src/MainPage"
}
```
Together, these declarations tell Wasp that when a user navigates to `/`, it should render the named export from `src/MainPage.{jsx,tsx}`.
### The MainPage Component
Let's take a look at the React component referenced by the page declaration:
```tsx title="src/MainPage.tsx" auto-js
export function MainPage() {
// ...
};
```
This is a regular functional React component. It also imports some CSS and a logo from the `assets` folder.
That is all the code you need! Wasp takes care of everything else necessary to define, build, and run the web app.
### Adding a Second Page
To add more pages, you can create another set of **page** and **route** declarations. You can even add parameters to the URL path, using the same syntax as [React Router](https://reactrouter.com/7.12.0/start/declarative/routing#dynamic-segments). Let's test this out by adding a new page:
```wasp title="main.wasp"
route HelloRoute { path: "/hello/:name", to: HelloPage }
page HelloPage {
component: import { HelloPage } from "@src/HelloPage"
}
```
When a user visits `/hello/their-name`, Wasp renders the component exported from `src/HelloPage.{jsx,tsx}` and you can use the `useParams` hook from `react-router` to access the `name` parameter:
```tsx title="src/HelloPage.tsx" auto-js
export const HelloPage = () => {
const { name } = useParams<"name">();
return
Here's {name}!
;
};
```
Now you can visit `/hello/johnny` and see "Here's johnny!"
:::tip Type-safe links
Since you are using Typescript, you can benefit from using Wasp's type-safe `Link` component and the `routes` object. Check out the [type-safe links docs](../advanced/links) for more details.
:::
### Cleaning Up
Now that you've seen how Wasp deals with Routes and Pages, it's finally time to build the Todo app.
Start by cleaning up the starter project and removing unnecessary code and files.
First, remove most of the code from the `MainPage` component:
```tsx title="src/MainPage.tsx" auto-js
export const MainPage = () => {
return
Hello world!
;
};
```
At this point, the main page should look like this:
You can now delete redundant files: `src/Main.css`, `src/assets/logo.svg`, and `src/HelloPage.{jsx,tsx}` (we won't need this page for the rest of the tutorial).
Since `src/HelloPage.{jsx,tsx}` no longer exists, remove its `route` and `page` declarations from the `main.wasp` file.
Your Wasp file should now look like this:
```wasp title="main.wasp"
app TodoApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "TodoApp",
head: [
"",
]
}
route RootRoute { path: "/", to: MainPage }
page MainPage {
component: import { MainPage } from "@src/MainPage"
}
```
Excellent work!
You now have a basic understanding of Wasp and are ready to start building your TodoApp.
We'll implement the app's core features in the following sections.
## 4. Database Entities
Entities are one of the most important concepts in Wasp and are how you define what gets stored in the database.
Wasp uses Prisma to talk to the database, and you define Entities by defining Prisma models in the `schema.prisma` file.
Since our Todo app is all about tasks, we'll define a Task entity by adding a Task model in the `schema.prisma` file:
```prisma title="schema.prisma"
// ...
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
}
```
:::note
Read more about how Wasp Entities work in the [Entities](../data-model/entities.md) section or how Wasp uses the `schema.prisma` file in the [Prisma Schema File](../data-model/prisma-file.md) section.
:::
To update the database schema to include this entity, stop the `wasp start` process, if it's running, and run:
```sh
wasp db migrate-dev
```
You'll need to do this any time you change an entity's definition. It instructs Prisma to create a new database migration and apply it to the database.
To take a look at the database and the new `Task` entity, run:
```sh
wasp db studio
```
This will open a new page in your browser to view and edit the data in your database.
Click on the `Task` entity and check out its fields! We don't have any data in our database yet, but we are about to change that.
## 5. Querying the Database
We want to know which tasks we need to do, so let's list them!
The primary way of working with Entities in Wasp is with [Queries and Actions](../data-model/operations/overview), collectively known as **_Operations_**.
Queries are used to read an entity, while Actions are used to create, modify, and delete entities. Since we want to list the tasks, we'll want to use a Query.
To list the tasks, you must:
1. Create a Query that fetches the tasks from the database.
2. Update the `MainPage.{jsx,tsx}` to use that Query and display the results.
### Defining the Query
We'll create a new Query called `getTasks`. We'll need to declare the Query in the Wasp file and write its implementation in JSTS.
#### Declaring a Query
We need to add a **query** declaration to `main.wasp` so that Wasp knows it exists:
```wasp title="main.wasp"
// ...
query getTasks {
// Specifies where the implementation for the query function is.
// The path `@src/queries` resolves to `src/queries.js`.
// No need to specify an extension.
fn: import { getTasks } from "@src/queries",
// Tell Wasp that this query reads from the `Task` entity. Wasp will
// automatically update the results of this query when tasks are modified.
entities: [Task]
}
```
```wasp title="main.wasp"
// ...
query getTasks {
// Specifies where the implementation for the query function is.
// The path `@src/queries` resolves to `src/queries.ts`.
// No need to specify an extension.
fn: import { getTasks } from "@src/queries",
// Tell Wasp that this query reads from the `Task` entity. Wasp will
// automatically update the results of this query when tasks are modified.
entities: [Task]
}
```
:::note
To generate the types used in the next section, make sure that `wasp start` is still running.
:::
#### Implementing a Query
Next, create a new file called `src/queries.js` and define the JavaScript function we've just imported in our `query` declaration:
Next, create a new file called `src/queries.ts` and define the TypeScript function we've just imported in our `query` declaration:
```ts title="src/queries.ts" auto-js
export const getTasks: GetTasks = async (args, context) => {
return context.entities.Task.findMany({
orderBy: { id: "asc" },
});
};
```
Wasp automatically generates the types `GetTasks` and `Task` based on the contents of `main.wasp`:
- `Task` is a type corresponding to the `Task` entity you defined in `schema.prisma`.
- `GetTasks` is a generic type Wasp automatically generated based on the `getTasks` Query you defined in `main.wasp`.
You can use these types to specify the Query's input and output types. This Query doesn't expect any arguments (its input type is `void`), but it does return an array of tasks (its output type is `Task[]`).
Annotating the Queries is optional, but highly recommended because doing so enables **full-stack type safety**. We'll see what this means in the next step.
Query function parameters:
- `args: object`
The arguments the caller passes to the Query.
- `context`
An object with extra information injected by Wasp. Its type depends on the Query declaration.
Since the Query declaration in `main.wasp` says that the `getTasks` Query uses `Task` entity, Wasp injected a [Prisma client](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud) for the `Task` entity as `context.entities.Task` - we used it above to fetch all the tasks from the database.
:::info
Queries and Actions are NodeJS functions executed on the server.
:::
### Invoking the Query On the Frontend
While we implement Queries on the server, Wasp generates client-side functions that automatically take care of serialization, network calls, and cache invalidation, allowing you to call the server code like it's a regular function.
This makes it easy for us to use the `getTasks` Query we just created in our React component:
```tsx title="src/MainPage.tsx" auto-js
// highlight-next-line
export const MainPage = () => {
// highlight-start
const { data: tasks, isLoading, error } = useQuery(getTasks);
return (
);
};
// highlight-end
```
Most of this code is regular React, the only exception being the twothree special `wasp` imports:
- `getTasks` - The client-side Query function Wasp generated based on the `getTasks` declaration in `main.wasp`.
- `useQuery` - Wasp's [useQuery](../data-model/operations/queries#the-usequery-hook-1) React hook, which is based on [react-query](https://github.com/tannerlinsley/react-query)'s hook with the same name.
- `getTasks` - The client-side Query function Wasp generated based on the `getTasks` declaration in `main.wasp`.
- `useQuery` - Wasp's [useQuery](../data-model/operations/queries#the-usequery-hook-1) React hook, which is based on [react-query](https://github.com/tannerlinsley/react-query)'s hook with the same name.
- `Task` - The type for the Task entity defined in `schema.prisma`.
Notice how you don't need to annotate the type of the Query's return value: Wasp uses the types you defined while implementing the Query for the generated client-side function. This is **full-stack type safety**: the types on the client always match the types on the server.
We could have called the Query directly using `getTasks()`, but the `useQuery` hook makes it reactive: React will re-render the component every time the Query changes. Remember that Wasp automatically refreshes Queries whenever the data is modified.
With these changes, you should be seeing the text "No tasks" on the screen:
We'll create a form to add tasks in the next step đĒ
## 6. Modifying Data
In the previous section, you learned about using Queries to fetch data.
Let's now learn about Actions so you can add and update tasks in the database.
In this section, you will create:
1. A Wasp Action that creates a new task.
2. A React form that calls that Action when the user creates a task.
### Creating a New Action
Creating an Action is very similar to creating a Query.
#### Declaring an Action
We must first declare the Action in `main.wasp`:
```wasp title="main.wasp"
// ...
action createTask {
fn: import { createTask } from "@src/actions",
entities: [Task]
}
```
#### Implementing an Action
Let's now define a JavaScriptTypeScript function for our `createTask` Action:
```ts title="src/actions.ts" auto-js
type CreateTaskPayload = Pick;
export const createTask: CreateTask = async (
args,
context,
) => {
return context.entities.Task.create({
data: { description: args.description },
});
};
```
Once again, we've annotated the Action with the `CreateTask` and `Task` types generated by Wasp. Just like with queries, defining the types on the implementation makes them available on the frontend, giving us **full-stack type safety**.
:::tip
We put the function in a new file `src/actions.{js,ts}`, but we could have put it anywhere we wanted! There are no limitations here, as long as the declaration in the Wasp file imports it correctly and the file is located within `src` directory.
:::
### Invoking the Action on the Client
Start by defining a form for creating new tasks.
```tsx title="src/MainPage.tsx" auto-js
// highlight-next-line
createTask,
getTasks,
useQuery,
} from "wasp/client/operations";
// ... MainPage, TaskView, TaskList ...
// highlight-start
const NewTaskForm = () => {
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
try {
const target = event.target as HTMLFormElement;
const description = target.description.value;
target.reset();
await createTask({ description });
} catch (err: any) {
window.alert("Error: " + err.message);
}
};
return (
);
};
// highlight-end
```
Unlike Queries, you can call Actions directly (without wrapping them in a hook) because they don't need reactivity. The rest is just regular React code.
Finally, because we've previously annotated the Action's server implementation with the correct type, Wasp knows that the `createTask` Action expects a value of type `{ description: string }` (try changing the argument and reading the error message). Wasp also knows that a call to the `createTask` Action returns a `Task` but are not using it in this example.
All that's left now is adding this form to the page component:
```tsx title="src/MainPage.tsx" auto-js
const MainPage = () => {
const { data: tasks, isLoading, error } = useQuery(getTasks);
return (
);
};
// ... TaskList, TaskView, NewTaskForm ...
```
Great work!
You now have a form for creating new tasks.
Try creating a "Build a Todo App in Wasp" task and see it appear in the list below. The task is created on the server and saved in the database.
Try refreshing the page or opening it in another browser. You'll see the tasks are still there!
:::note Automatic Query Invalidation
When you create a new task, the list of tasks is automatically updated to display the new task, even though you haven't written any code that does that! Wasp handles these automatic updates under the hood.
When you declared the `getTasks` and `createTask` operations, you specified that they both use the `Task` entity. So when `createTask` is called, Wasp knows that the data `getTasks` fetches may have changed and automatically updates it in the background. This means that **out of the box, Wasp keeps all your queries in sync with any changes made through Actions**.
This behavior is convenient as a default but can cause poor performance in large apps. While there is no mechanism for overriding this behavior yet, it is something that we plan to include in Wasp in the future. This feature is tracked [here](https://github.com/wasp-lang/wasp/issues/63).
:::
### A Second Action
Our Todo app isn't finished if you can't mark a task as done.
We'll create a new Action to update a task's status and call it from React whenever a task's checkbox is toggled.
Since we've already created one task together, try to create this one yourself. It should be an Action named `updateTask` that receives the task's `id` and its `isDone` status. You can see our implementation below.
Declaring the Action in `main.wasp`:
```wasp title="main.wasp"
// ...
action updateTask {
fn: import { updateTask } from "@src/actions",
entities: [Task]
}
```
Implementing the Action on the server:
```ts title="src/actions.ts" auto-js
// ...
type UpdateTaskPayload = Pick;
export const updateTask: UpdateTask = async (
{ id, isDone },
context,
) => {
return context.entities.Task.update({
where: { id },
data: {
isDone: isDone,
},
});
};
```
You can now call `updateTask` from the React component:
```tsx title="src/MainPage.tsx" auto-js
// highlight-next-line
updateTask,
createTask,
getTasks,
useQuery,
} from "wasp/client/operations";
// ... MainPage ...
const TaskView = ({ task }: { task: Task }) => {
// highlight-start
const handleIsDoneChange = async (event: ChangeEvent) => {
try {
await updateTask({
id: task.id,
isDone: event.target.checked,
});
} catch (error: any) {
window.alert("Error while updating task: " + error.message);
}
};
// highlight-end
return (
{task.description}
);
};
// ... TaskList, NewTaskForm ...
```
Awesome!
You can now mark this task as done.
It's time to make one final addition to your app: supporting multiple users.
## 7. Adding Authentication
Most modern apps need a way to create and authenticate users. Wasp makes this as easy as possible with its first-class auth support.
To add users to your app, you must:
- [ ] Create a `User` Entity.
- [ ] Tell Wasp to use the _Username and Password_ authentication.
- [ ] Add login and signup pages.
- [ ] Update the main page to require authentication.
- [ ] Add a relation between `User` and `Task` entities.
- [ ] Modify your Queries and Actions so users can only see and modify their tasks.
- [ ] Add a logout button.
### Creating a User Entity
Since Wasp manages authentication, it will create [the auth related entities](../auth/entities) for you in the background. Nothing to do here!
You must only add the `User` Entity to keep track of who owns which tasks:
```prisma title="schema.prisma"
// ...
model User {
id Int @id @default(autoincrement())
}
```
### Adding Auth to the Project
Next, tell Wasp to use full-stack [authentication](../auth/overview):
```wasp title="main.wasp"
app TodoApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "TodoApp",
head: [
"",
],
// highlight-start
auth: {
// Tells Wasp which entity to use for storing users.
userEntity: User,
methods: {
// Enable username and password auth.
usernameAndPassword: {}
},
// We'll see how this is used in a bit.
onAuthFailedRedirectTo: "/login"
}
// highlight-end
}
// ...
```
Don't forget to update the database schema by running:
```sh
wasp db migrate-dev
```
By doing this, Wasp will create:
- [Auth UI](../auth/ui) with login and signup forms.
- A `logout()` action.
- A React hook `useAuth()`.
- `context.user` for use in Queries and Actions.
:::info
Wasp also supports authentication using [Google](../auth/social-auth/google), [GitHub](../auth/social-auth/github), and [email](../auth/email), with more on the way!
:::
### Adding Login and Signup Pages
Wasp creates the login and signup forms for us, but we still need to define the pages to display those forms on. We'll start by declaring the pages in the Wasp file:
```wasp title="main.wasp"
// ...
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@src/SignupPage"
}
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@src/LoginPage"
}
```
Great, Wasp now knows these pages exist!
Here's the React code for the pages you've just imported:
```tsx title="src/LoginPage.tsx" auto-js
export const LoginPage = () => {
return (
I don't have an account yet (go to signup).
);
};
```
The signup page is very similar to the login page:
```tsx title="src/SignupPage.tsx" auto-js
export const SignupPage = () => {
return (
I already have an account (go to login).
);
};
```
:::tip Type-safe links
Since you are using Typescript, you can benefit from using Wasp's type-safe `Link` component and the `routes` object. Check out the [type-safe links docs](../advanced/links) for more details.
:::
### Update the Main Page to Require Auth
We don't want users who are not logged in to access the main page, because they won't be able to create any tasks. So let's make the page private by requiring the user to be logged in:
```wasp title="main.wasp"
// ...
page MainPage {
// highlight-next-line
authRequired: true,
component: import { MainPage } from "@src/MainPage"
}
```
Now that auth is required for this page, unauthenticated users will be redirected to `/login`, as we specified with `app.auth.onAuthFailedRedirectTo`.
Additionally, when `authRequired` is `true`, the page's React component will be provided a `user` object as prop.
```tsx title="src/MainPage.tsx" auto-js
// highlight-next-line
export const MainPage = ({ user }: { user: AuthUser }) => {
// Do something with the user
// ...
};
```
Ok, time to test this out. Navigate to the main page (`/`) of the app. You'll get redirected to `/login`, where you'll be asked to authenticate.
Since we just added users, you don't have an account yet. Go to the signup page and create one. You'll be sent back to the main page where you will now be able to see the TODO list!
Let's check out what the database looks like. Start the Prisma Studio:
```shell
wasp db studio
```
You'll notice that we now have a `User` entity in the database alongside the `Task` entity.
However, you will notice that if you try logging in as different users and creating some tasks, all users share the same tasks. That's because you haven't yet updated the queries and actions to have per-user tasks. Let's do that next.
You might notice some extra Prisma models like `Auth`, `AuthIdentity` and `Session` that Wasp created for you. You don't need to care about these right now, but if you are curious, you can read more about them [here](../auth/entities).
### Defining a User-Task Relation
First, let's define a one-to-many relation between users and tasks (check the [Prisma docs on relations](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/relations)):
```prisma title="schema.prisma"
// ...
model User {
id Int @id @default(autoincrement())
// highlight-next-line
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
// highlight-next-line
user User? @relation(fields: [userId], references: [id])
// highlight-next-line
userId Int?
}
```
As always, you must migrate the database after changing the Entities:
```sh
wasp db migrate-dev
```
:::note
We made `user` and `userId` in `Task` optional (via `?`) because that allows us to keep the existing tasks, which don't have a user assigned, in the database.
This isn't recommended because it allows an unwanted state in the database (what is the purpose of the task not belonging to anybody?) and normally we would not make these fields optional.
Instead, we would do a data migration to take care of those tasks, even if it means just deleting them all. However, for this tutorial, for the sake of simplicity, we will stick with this.
:::
### Updating Operations to Check Authentication
Next, let's update the queries and actions to forbid access to non-authenticated users and to operate only on the currently logged-in user's tasks:
```ts title="src/queries.ts" auto-js
// highlight-next-line
export const getTasks: GetTasks = async (args, context) => {
// highlight-start
if (!context.user) {
throw new HttpError(401);
}
// highlight-end
return context.entities.Task.findMany({
// highlight-next-line
where: { user: { id: context.user.id } },
orderBy: { id: "asc" },
});
};
```
```ts title="src/actions.ts" auto-js
// highlight-next-line
type CreateTaskPayload = Pick;
export const createTask: CreateTask = async (
args,
context,
) => {
// highlight-start
if (!context.user) {
throw new HttpError(401);
}
// highlight-end
return context.entities.Task.create({
data: {
description: args.description,
// highlight-next-line
user: { connect: { id: context.user.id } },
},
});
};
type UpdateTaskPayload = Pick;
export const updateTask: UpdateTask<
UpdateTaskPayload,
{ count: number }
> = async (args, context) => {
// highlight-start
if (!context.user) {
throw new HttpError(401);
}
// highlight-end
return context.entities.Task.updateMany({
where: { id: args.id, user: { id: context.user.id } },
data: { isDone: args.isDone },
});
};
```
:::note
Due to how Prisma works, we had to convert `update` to `updateMany` in `updateTask` action to be able to specify the user id in `where`.
:::
With these changes, each user should have a list of tasks that only they can see and edit.
Try playing around, adding a few users and some tasks for each of them. Then open the DB studio:
```sh
wasp db studio
```
You will see that each user has their tasks, just as we specified in our code!
### Logout Button
Last, but not least, let's add the logout functionality:
```tsx title="src/MainPage.tsx" auto-js with-hole
// ...
// highlight-next-line
//...
const MainPage = () => {
// ...
return (
{$HOLE$}
// highlight-next-line
);
};
```
This is it, we have a working authentication system, and our Todo app is multi-user!
### What's Next?
We did it đ You've followed along with this tutorial to create a basic Todo app with Wasp.
You can find the complete code for the JS version of the tutorial [here](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoApp).
You can find the complete code for the TS version of the tutorial [here](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoAppTs).
You should be ready to learn about more complicated features and go more in-depth with the features already covered. Scroll through the sidebar on the left side of the page to see every feature Wasp has to offer. Or, let your imagination run wild and start building your app! â¨
Looking for inspiration?
- Get a jump start on your next project with [Starter Templates](../project/starter-templates).
- Check out our [official examples](https://github.com/wasp-lang/wasp/tree/release/examples).
- Make a real-time app with [Web Sockets](../advanced/web-sockets).
:::note
If you notice that some of the features you'd like to have are missing, or have any other kind of feedback, please write to us on [Discord](https://discord.gg/rzdnErX) or create an issue on [Github](https://github.com/wasp-lang/wasp), so we can learn which features to add/improve next đ
If you would like to contribute or help to build a feature, let us know! You can find more details on contributing [here](contributing.md).
:::
Oh, and do [**subscribe to our newsletter**](/#signup)! We usually send one per month, and Matija does his best to unleash his creativity to make them engaging and fun to read :D!
------
# Data Model
## Entities
Entities are the foundation of your app's data model. In short, an Entity defines a model in your database.
Wasp uses the excellent [Prisma ORM](https://www.prisma.io/) to implement all database functionality and occasionally enhances it with a thin abstraction layer. This means that you use the `schema.prisma` file to define your database models and relationships. Wasp understands the Prisma schema file and picks up all the models you define there. You can read more about this in the [Prisma Schema File](./prisma-file.md) section of the docs.
In your project, you'll find a `schema.prisma` file in the root directory:
```
.
âââ main.wasp
...
âââ schema.prisma
âââ src
âââ tsconfig.json
âââ vite.config.ts
```
Prisma uses the _Prisma Schema Language_, a simple definition language explicitly created for defining models.
The language is declarative and very intuitive. We'll also go through an example later in the text, so there's no need to go and thoroughly learn it right away. Still, if you're curious, look no further than Prisma's official documentation:
- [Basic intro and examples](https://www.prisma.io/docs/orm/prisma-schema/overview)
- [A more exhaustive language specification](https://www.prisma.io/docs/orm/reference/prisma-schema-reference)
### Defining an Entity
A Prisma `model` declaration in the `schema.prisma` file represents a Wasp Entity.
Entity vs Model
You might wonder why we distinguish between a **Wasp Entity** and a **Prisma model** if they're essentially the same thing right now.
While defining a Prisma model is currently the only way to create an Entity in Wasp, the Entity concept is a higher-level abstraction. We plan to expand on Entities in the future, both in terms of how you can define them and what you can do with them.
So, think of an Entity as a Wasp concept and a model as a Prisma concept. For now, all Prisma models are Entities and vice versa, but this relationship might evolve as Wasp grows.
Here's how you could define an Entity that represents a Task:
```prisma title="schema.prisma"
model Task {
id String @id @default(uuid())
description String
isDone Boolean @default(false)
}
```
```prisma title="schema.prisma"
model Task {
id String @id @default(uuid())
description String
isDone Boolean @default(false)
}
```
The above Prisma `model` definition tells Wasp to create a table for storing Tasks where each task has three fields (i.e., the `tasks` table has three columns):
- `id` - A string value serving as a primary key. The database automatically generates it by generating a random unique ID.
- `description` - A string value for storing the task's description.
- `isDone` - A boolean value indicating the task's completion status. If you don't set it when creating a new task, the database sets it to `false` by default.
Wasp also exposes a type for working with the created Entity. You can import and use it like this:
```ts
import { Task } from 'wasp/entities'
const task: Task = { ... }
// You can also define functions for working with entities
function getInfoMessage(task: Task): string {
const isDoneText = task.isDone ? "is done" : "is not done"
return `Task '${task.description}' is ${isDoneText}.`
}
```
Using the `Task` type in `getInfoMessageInfo`'s definition connects the argument's type with the `Task` entity.
This coupling removes duplication and ensures the function keeps the correct signature even if you change the entity. Of course, the function might throw type errors depending on how you change it, but that's precisely what you want!
Entity types are available everywhere, including the client code:
```ts
import { Task } from "wasp/entities"
export function ExamplePage() {
const task: Task = {
id: 123,
description: "Some random task",
isDone: false,
}
return
{task.description}
}
```
The mentioned type safety mechanisms also apply here: changing the task entity in our `schema.prisma` file changes the imported type, which might throw a type error and warn us that our task definition is outdated.
You'll learn even more about Entity types when you start using [them with operations](#using-entities-in-operations).
#### Working with Entities
Let's see how you can define and work with Wasp Entities:
1. Create/update some Entities in the `schema.prisma` file.
2. Run `wasp db migrate-dev`. This command syncs the database model with the Entity definitions the `schema.prisma` file. It does this by creating migration scripts.
3. Migration scripts are automatically placed in the `migrations/` folder. Make sure to commit this folder into version control.
4. Use Wasp's JavasScript API to work with the database when implementing Operations (we'll cover this in detail when we talk about [operations](../data-model/operations/overview)).
##### Using Entities in Operations
Most of the time, you will be working with Entities within the context of [Operations (Queries & Actions)](../data-model/operations/overview). We'll see how that's done on the next page.
##### Using Entities directly
If you need more control, you can directly interact with Entities by importing and using the [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud). We recommend sticking with conventional Wasp-provided mechanisms, only resorting to directly using the Prisma client only if you need a feature Wasp doesn't provide.
You can only use the Prisma Client in your Wasp server code. You can import it like this:
```js
import { prisma } from 'wasp/server'
prisma.task.create({
description: "Read the Entities doc",
isDone: true // almost :)
})
```
```ts
import { prisma } from 'wasp/server'
prisma.task.create({
description: "Read the Entities doc",
isDone: true // almost :)
})
```
:::note Available Prisma features in the client
While the Prisma Client is not available in your client code, you can still import Prisma there, for accessing
type definitions (notably, `enum`s).
You can see more information in the overview of [supported Prisma Schema features](./prisma-file.md#the-enum-blocks).
:::
#### Next steps
Now that we've seen how to define Entities that represent Wasp's core data model, we'll see how to make the most of them in other parts of Wasp. Keep reading to learn all about Wasp Operations!
## Operations Overview
While Entities enable you to define your app's data model and relationships, Operations are all about working with this data.
There are two kinds of Operations: [Queries](../../data-model/operations/queries.md) and [Actions](../../data-model/operations/actions.md). As their names suggest,
Queries are meant for reading data, and Actions are meant for changing it (either by updating existing entries or creating new ones).
Keep reading to find out all there is to know about Operations in Wasp.
## Queries
We'll explain what Queries are and how to use them. If you're looking for a detailed API specification, skip ahead to the [API Reference](#api-reference).
You can use Queries to fetch data from the server. They shouldn't modify the server's state.
Fetching all comments on a blog post, a list of users that liked a video, information about a single product based on its ID... All of these are perfect use cases for a Query.
:::tip
Queries are fairly similar to Actions in terms of their API.
Therefore, if you're already familiar with Actions, you might find reading the entire guide repetitive.
We instead recommend skipping ahead and only reading [the differences between Queries and Actions](../../data-model/operations/actions#differences-between-queries-and-actions), and consulting the [API Reference](#api-reference) as needed.
:::
### Working with Queries
You declare queries in the `.wasp` file and implement them using NodeJS. Wasp not only runs these queries within the server's context but also creates code that enables you to call them from any part of your codebase, whether it's on the client or server side.
This means you don't have to build an HTTP API for your query, manage server-side request handling, or even deal with client-side response handling and caching.
Instead, just concentrate on implementing the business logic inside your query, and let Wasp handle the rest!
To create a Query, you must:
1. Declare the Query in Wasp using the `query` declaration.
2. Define the Query's NodeJS implementation.
After completing these two steps, you'll be able to use the Query from any point in your code.
#### Declaring Queries
To create a Query in Wasp, we begin with a `query` declaration.
Let's declare two Queries - one to fetch all tasks, and another to fetch tasks based on a filter, such as whether a task is done:
```wasp title="main.wasp"
// ...
query getAllTasks {
fn: import { getAllTasks } from "@src/queries"
}
query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries"
}
```
```wasp title="main.wasp"
// ...
query getAllTasks {
fn: import { getAllTasks } from "@src/queries"
}
query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries"
}
```
If you want to know about all supported options for the `query` declaration, take a look at the [API Reference](#api-reference).
The names of Wasp Queries and their implementations don't need to match, but we'll keep them the same to avoid confusion.
:::info
You might have noticed that we told Wasp to import Query implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `queries.{js,ts}` in the next section.
It's a good idea to start with the high-level concept (the Query declaration in the Wasp file) and only then deal with the implementation details (the Query's implementation in JavaScript).
:::
After declaring a Wasp Query, two important things happen:
- Wasp **generates a server-side NodeJS function** that shares its name with the Query.
- Wasp **generates a client-side JavaScript function** that shares its name with the Query (e.g., `getFilteredTasks`).
This function takes a single optional argument - an object containing any serializable data you wish to use inside the Query.
Wasp will send this object over the network and pass it into the Query's implementation as its first positional argument (more on this when we look at the implementations).
Such an abstraction works thanks to an HTTP API route handler Wasp generates on the server, which calls the Query's NodeJS implementation under the hood.
Generating these two functions ensures a similar calling interface across the entire app (both client and server).
#### Implementing Queries in Node
Now that we've declared the Query, what remains is to implement it.
We've instructed Wasp to look for the Queries' implementations in the file `src/queries.{js,ts}`, so that's where we should export them from.
Here's how you might implement the previously declared Queries `getAllTasks` and `getFilteredTasks`:
```js title="src/queries.js"
// our "database"
const tasks = [
{ id: 1, description: 'Buy some eggs', isDone: true },
{ id: 2, description: 'Make an omelette', isDone: false },
{ id: 3, description: 'Eat breakfast', isDone: false },
]
// You don't need to use the arguments if you don't need them
export const getAllTasks = () => {
return tasks
}
// The 'args' object is something sent by the caller (most often from the client)
export const getFilteredTasks = (args) => {
const { isDone } = args
return tasks.filter((task) => task.isDone === isDone)
}
```
```ts title="src/queries.ts"
import { type GetAllTasks, type GetFilteredTasks } from 'wasp/server/operations'
type Task = {
id: number
description: string
isDone: boolean
}
// our "database"
const tasks: Task[] = [
{ id: 1, description: 'Buy some eggs', isDone: true },
{ id: 2, description: 'Make an omelette', isDone: false },
{ id: 3, description: 'Eat breakfast', isDone: false },
]
// You don't need to use the arguments if you don't need them
export const getAllTasks: GetAllTasks = () => {
return tasks
}
// The 'args' object is something sent by the caller (most often from the client)
export const getFilteredTasks: GetFilteredTasks<
Pick,
Task[]
> = (args) => {
const { isDone } = args
return tasks.filter((task) => task.isDone === isDone)
}
```
#### Type support for Queries
Wasp automatically generates the types `GetAllTasks` and `GetFilteredTasks` based on your Wasp file's declarations:
- `GetAllTasks` is a generic type automatically generated by Wasp, based on the Query declaration for `getAllTasks`.
- `GetFilteredTasks` is also a generic type automatically generated by Wasp, based on the Query declaration for `getFilteredTasks`.
Use these types to type the Query's implementation.
It's optional but very helpful since doing so properly types the Query's context.
In this case, TypeScript will know the `context.entities` object must include the `Task` entity.
TypeScript also knows whether the `context` object includes user information (it depends on whether your Query uses auth).
The generated types are generic and accept two optional type arguments: `Input` and `Output`.
1. `Input` - The argument (the payload) received by the Query function.
2. `Output` - The Query function's return type.
Use these type arguments to type the Query's inputs and outputs.
Explanation for the example above
The above code says that the Query `getAllTasks` doesn't expect any arguments (its input type is `void`), but it does return a list of tasks (its output type is `Task[]`).
On the other hand, the Query `getFilteredTasks` expects an object of type `{ isDone: boolean }`. This type is derived from the `Task` entity type.
If you don't care about typing the Query's inputs and outputs, you can omit both type arguments. TypeScript will then infer the most general types (`never` for the input and `unknown` for the output).
Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you:
- Type support for the arguments and the return value inside the implementation.
- **Full-stack type safety**. We'll explore what this means when we discuss calling the Query from the client.
Read more about type support for implementing Queries in the [API Reference](#implementing-queries).
:::tip Inferring the return type
If don't want to explicitly type the Query's return value, the `satisfies` keyword tells TypeScript to infer it automatically:
```typescript
const getFoo = (async (_args, context) => {
const foos = await context.entities.Foo.findMany()
return {
foos,
message: 'Here are some foos!',
queriedAt: new Date(),
}
}) satisfies GetFoo
```
From the snippet above, TypeScript knows:
1. The correct type for `context`.
2. The Query's return type is `{ foos: Foo[], message: string, queriedAt: Date }`.
If you don't need the context, you can skip specifying the Query's type (and arguments):
```typescript
const getFoo = () => {{ name: 'Foo', date: new Date() }}
```
:::
For a detailed explanation of the Query definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference).
#### Using Queries
##### Using Queries on the client
To call a Query on the client, you can import it from `wasp/client/operations` and call it directly.
The usage doesn't change depending on whether the Query is authenticated or not.
Wasp authenticates the logged-in user in the background.
```js
import { getAllTasks, getFilteredTasks } from 'wasp/client/operations'
// ...
const allTasks = await getAllTasks()
const doneTasks = await getFilteredTasks({ isDone: true })
```
```ts
import { getAllTasks, getFilteredTasks } from 'wasp/client/operations'
// TypeScript automatically infers the return values and type-checks
// the payloads.
const allTasks = await getAllTasks()
const doneTasks = await getFilteredTasks({ isDone: true })
```
Wasp supports **automatic full-stack type safety**.
You only need to specify the Query's type in its server-side definition, and the client code will automatically know its API payload types.
##### Using Queries on the server
Calling a Query on the server is similar to calling it on the client.
Here's what you have to do differently:
- Import Queries from `wasp/server/operations` instead of `wasp/client/operations`.
- Make sure you pass in a `context` object with the `user` field to authenticated Queries.
- Note that you don't have to pass other parts of the `context` object, like Entities, those will get injected automatically.
```js
import { getAllTasks, getFilteredTasks } from 'wasp/server/operations'
const user = // Get an AuthUser object, e.g., from context.user in an operation.
// ...
const allTasks = await getAllTasks({ user })
const doneTasks = await getFilteredTasks({ isDone: true }, { user })
```
```ts
import { getAllTasks, getFilteredTasks } from 'wasp/server/operations'
const user = // Get an AuthUser object, e.g., from context.user in an operation.
// TypeScript automatically infers the return values and type-checks
// the payloads.
const allTasks = await getAllTasks({ user })
const doneTasks = await getFilteredTasks({ isDone: true }, { user })
```
##### The `useQuery` hook
When using Queries on the client, you can make them reactive with the `useQuery` hook.
This hook comes bundled with Wasp and is a thin wrapper around the `useQuery` hook from [_react-query_](https://github.com/tannerlinsley/react-query). The only difference is that you don't need to supply the key - Wasp handles this for you automatically.
Here's an example of calling the Queries using the `useQuery` hook:
```jsx title="src/MainPage.jsx"
import React from 'react'
import { useQuery, getAllTasks, getFilteredTasks } from 'wasp/client/operations'
const MainPage = () => {
const { data: allTasks, error: error1 } = useQuery(getAllTasks)
const { data: doneTasks, error: error2 } = useQuery(getFilteredTasks, {
isDone: true,
})
if (error1 !== null || error2 !== null) {
return
)
}
export default MainPage
```
Notice how you don't need to annotate the Query's return value type. Wasp automatically infers the from the Query's backend implementation. This is **full-stack type safety**: the types on the client always match the types on the server.
For a detailed specification of the `useQuery` hook, check the [API Reference](#api-reference).
#### Error Handling
For security reasons, all exceptions thrown in the Query's NodeJS implementation are sent to the client as responses with the HTTP status code `500`, with all other details removed.
Hiding error details by default helps against accidentally leaking possibly sensitive information over the network.
If you do want to pass additional error information to the client, you can construct and throw an appropriate `HttpError` in your implementation:
```js title="src/queries.js"
import { HttpError } from 'wasp/server'
export const getAllTasks = async (args, context) => {
throw new HttpError(
403, // status code
"You can't do this!", // message
{ foo: 'bar' } // data
)
}
```
```ts title="src/queries.ts"
import { type GetAllTasks } from 'wasp/server/operations'
import { HttpError } from 'wasp/server'
export const getAllTasks: GetAllTasks = async (args, context) => {
throw new HttpError(
403, // status code
"You can't do this!", // message
{ foo: 'bar' } // data
)
}
```
If the status code is `4xx`, the client will receive a response object with the corresponding `message` and `data` fields, and it will rethrow the error (including these fields).
To prevent information leakage, the server won't forward these fields for any other HTTP status codes.
#### Using Entities in Queries
In most cases, resources used in Queries will be [Entities](../../data-model/entities.md).
To use an Entity in your Query, add it to the `query` declaration in Wasp:
```wasp {4,9} title="main.wasp"
query getAllTasks {
fn: import { getAllTasks } from "@src/queries",
entities: [Task]
}
query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries",
entities: [Task]
}
```
```wasp {4,9} title="main.wasp"
query getAllTasks {
fn: import { getAllTasks } from "@src/queries",
entities: [Task]
}
query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries",
entities: [Task]
}
```
Wasp will inject the specified Entity into the Query's `context` argument, giving you access to the Entity's Prisma API:
```js title="src/queries.js"
export const getAllTasks = async (args, context) => {
return context.entities.Task.findMany({})
}
export const getFilteredTasks = async (args, context) => {
return context.entities.Task.findMany({
where: { isDone: args.isDone },
})
}
```
```ts title="src/queries.ts"
import { type Task } from 'wasp/entities'
import { type GetAllTasks, type GetFilteredTasks } from 'wasp/server/operations'
export const getAllTasks: GetAllTasks = async (args, context) => {
return context.entities.Task.findMany({})
}
export const getFilteredTasks: GetFilteredTasks<
Pick,
Task[]
> = async (args, context) => {
return context.entities.Task.findMany({
where: { isDone: args.isDone },
})
}
```
Again, annotating the Queries is optional, but greatly improves **full-stack type safety**.
The object `context.entities.Task` exposes `prisma.task` from [Prisma's CRUD API](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud).
### API Reference
#### Declaring Queries
The `query` declaration supports the following fields:
- `fn: ExtImport` Required!
The import statement of the Query's NodeJs implementation.
- `entities: [Entity]`
A list of entities you wish to use inside your Query.
For instructions on using Entities in Queries, take a look at [the guide](#using-entities-in-queries).
##### Example
Declaring the Query:
```wasp
query getFoo {
fn: import { getFoo } from "@src/queries"
entities: [Foo]
}
```
Enables you to import and use it anywhere in your code (on the server or the client):
```js
// Use it on the client
import { getFoo } from 'wasp/client/operations'
// Use it on the server
import { getFoo } from 'wasp/server/operations'
```
On the client, the Query expects
Declaring the Query:
```wasp
query getFoo {
fn: import { getFoo } from "@src/queries"
entities: [Foo]
}
```
Enables you to import and use it anywhere in your code (on the server or the client):
```ts
// Use it on the client
import { getFoo } from 'wasp/client/operations'
// Use it on the server
import { getFoo } from 'wasp/server/operations'
```
And also creates a type you can import on the server:
```ts
import { type GetFoo } from 'wasp/server/operations'
```
#### Implementing Queries
The Query's implementation is a NodeJS function that takes two arguments (it can be an `async` function if you need to use the `await` keyword).
Since both arguments are positional, you can name the parameters however you want, but we'll stick with `args` and `context`:
1. `args` (type depends on the Query)
An object containing the data **passed in when calling the query** (e.g., filtering conditions).
Check [the usage examples](#using-queries) to see how to pass this object to the Query.
2. `context` (type depends on the Query)
An additional context object **passed into the Query by Wasp**. This object contains user session information, as well as information about entities. Check the [section about using entities in Queries](#using-entities-in-queries) to see how to use the entities field on the `context` object, or the [auth section](../../auth/overview#using-the-contextuser-object) to see how to use the `user` object.
After you [declare the query](#declaring-queries), Wasp generates a generic type you can use when defining its implementation.
For the Query declared as `getSomething`, the generated type is called `GetSomething`:
```ts
import { type GetSomething } from 'wasp/server/operations'
```
It expects two (optional) type arguments:
1. `Input`
The type of the `args` object (the Query's input payload). The default value is `never`.
2. `Output`
The type of the Query's return value (the Query's output payload). The default value is `unknown`.
The defaults were chosen to make the type signature as permissive as possible. If don't want your Query to take/return anything, use `void` as a type argument.
##### Example
The following Query:
```wasp
query getFoo {
fn: import { getFoo } from "@src/queries"
entities: [Foo]
}
```
Expects to find a named export `getFoo` from the file `src/queries.js`
```js title="queries.js"
export const getFoo = (args, context) => {
// implementation
}
```
The following Query:
```wasp
query getFoo {
fn: import { getFoo } from "@src/queries"
entities: [Foo]
}
```
Expects to find a named export `getFoo` from the file `src/queries.js`
You can use the generated type `GetFoo` and specify the Query's inputs and outputs using its type arguments.
```ts title="queries.ts"
import { type GetFoo } from 'wasp/server/operations'
type Foo = // ...
export const getFoo: GetFoo<{ id: number }, Foo> = (args, context) => {
// implementation
};
```
In this case, the Query expects to receive an object with an `id` field of type `number` (this is the type of `args`), and return a value of type `Foo` (this must match the type of the Query's return value).
#### The `useQuery` Hook
Wasp's `useQuery` hook is a thin wrapper around the `useQuery` hook from [_react-query_](https://github.com/tannerlinsley/react-query).
One key difference is that Wasp doesn't expect you to supply the cache key - it takes care of it under the hood.
Wasp's `useQuery` hook accepts three arguments:
- `queryFn` Required!
The client-side query function generated by Wasp based on a `query` declaration in your `.wasp` file.
- `queryFnArgs`
The arguments object (payload) you wish to pass into the Query. The Query's NodeJS implementation will receive this object as its first positional argument.
- `options`
A _react-query_ `options` object. Use this to change
[the default
behavior](https://react-query.tanstack.com/guides/important-defaults) for
this particular Query. If you want to change the global defaults, you can do
so in the [client setup function](../../project/client-config.md#overriding-default-behaviour-for-queries).
For an example of usage, check [this section](#the-usequery-hook).
## Actions
We'll explain what Actions are and how to use them. If you're looking for a detailed API specification, skip ahead to the [API Reference](#api-reference).
Actions are quite similar to [Queries](../../data-model/operations/queries.md), but with a key distinction: Actions are designed to modify and add data, while Queries are solely for reading data. Examples of Actions include adding a comment to a blog post, liking a video, or updating a product's price.
Actions and Queries work together to keep data caches up-to-date.
:::tip
Actions are almost identical to Queries in terms of their API.
Therefore, if you're already familiar with Queries, you might find reading the entire guide repetitive.
We instead recommend skipping ahead and only reading [the differences between Queries and Actions](#differences-between-queries-and-actions), and consulting the [API Reference](#api-reference) as needed.
:::
### Working with Actions
Actions are declared in Wasp and implemented in NodeJS. Wasp runs Actions within the server's context, but it also generates code that allows you to call them from anywhere in your code (either client or server) using the same interface.
This means you don't have to worry about building an HTTP API for the Action, managing server-side request handling, or even dealing with client-side response handling and caching.
Instead, just focus on developing the business logic inside your Action, and let Wasp handle the rest!
To create an Action, you need to:
1. Declare the Action in Wasp using the `action` declaration.
2. Implement the Action's NodeJS functionality.
Once these two steps are completed, you can use the Action from anywhere in your code.
#### Declaring Actions
To create an Action in Wasp, we begin with an `action` declaration. Let's declare two Actions - one for creating a task, and another for marking tasks as done:
```wasp title="main.wasp"
// ...
action createTask {
fn: import { createTask } from "@src/actions"
}
action markTaskAsDone {
fn: import { markTaskAsDone } from "@src/actions"
}
```
```wasp title="main.wasp"
// ...
action createTask {
fn: import { createTask } from "@src/actions"
}
action markTaskAsDone {
fn: import { markTaskAsDone } from "@src/actions"
}
```
If you want to know about all supported options for the `action` declaration, take a look at the [API Reference](#api-reference).
The names of Wasp Actions and their implementations don't necessarily have to match. However, to avoid confusion, we'll keep them the same.
:::info
You might have noticed that we told Wasp to import Action implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `actions.{js,ts}` in the next section.
It's a good idea to start with the high-level concept (the Action declaration in the Wasp file) and only then deal with the implementation details (the Action's implementation in JavaScript).
:::
After declaring a Wasp Action, two important things happen:
- Wasp **generates a server-side NodeJS function** that shares its name with the Action.
- Wasp **generates a client-side JavaScript function** that shares its name with the Action (e.g., `markTaskAsDone`).
This function takes a single optional argument - an object containing any serializable data you wish to use inside the Action.
Wasp will send this object over the network and pass it into the Action's implementation as its first positional argument (more on this when we look at the implementations).
Such an abstraction works thanks to an HTTP API route handler Wasp generates on the server, which calls the Action's NodeJS implementation under the hood.
Generating these two functions ensures a similar calling interface across the entire app (both client and server).
#### Implementing Actions in Node
Now that we've declared the Action, what remains is to implement it. We've instructed Wasp to look for the Actions' implementations in the file `src/actions.{js,ts}`, so that's where we should export them from.
Here's how you might implement the previously declared Actions `createTask` and `markTaskAsDone`:
```js title="src/actions.js"
// our "database"
let nextId = 4
const tasks = [
{ id: 1, description: 'Buy some eggs', isDone: true },
{ id: 2, description: 'Make an omelette', isDone: false },
{ id: 3, description: 'Eat breakfast', isDone: false },
]
// You don't need to use the arguments if you don't need them
export const createTask = (args) => {
const newTask = {
id: nextId,
isDone: false,
description: args.description,
}
nextId += 1
tasks.push(newTask)
return newTask
}
// The 'args' object is something sent by the caller (most often from the client)
export const markTaskAsDone = (args) => {
const task = tasks.find((task) => task.id === args.id)
if (!task) {
// We'll show how to properly handle such errors later
return
}
task.isDone = true
}
```
```ts title="src/actions.ts"
import { type CreateTask, type MarkTaskAsDone } from 'wasp/server/operations'
type Task = {
id: number
description: string
isDone: boolean
}
// our "database"
let nextId = 4
const tasks = [
{ id: 1, description: 'Buy some eggs', isDone: true },
{ id: 2, description: 'Make an omelette', isDone: false },
{ id: 3, description: 'Eat breakfast', isDone: false },
]
// You don't need to use the arguments if you don't need them
export const createTask: CreateTask, Task> = (
args
) => {
const newTask = {
id: nextId,
isDone: false,
description: args.description,
}
nextId += 1
tasks.push(newTask)
return newTask
}
// The 'args' object is something sent by the caller (most often from the client)
export const markTaskAsDone: MarkTaskAsDone, void> = (
args
) => {
const task = tasks.find((task) => task.id === args.id)
if (!task) {
// We'll show how to properly handle such errors later
return
}
task.isDone = true
}
```
#### Type support for Actions
Wasp automatically generates the types `CreateTask` and `MarkTaskAsDone` based on the declarations in your Wasp file:
- `CreateTask` is a generic type that Wasp automatically generated based on the Action declaration for `createTask`.
- `MarkTaskAsDone` is a generic type that Wasp automatically generated based on the Action declaration for `markTaskAsDone`.
Use these types to type the Action's implementation.
It's optional but very helpful since doing so properly types the Action's context.
In this case, TypeScript will know the `context.entities` object must include the `Task` entity.
TypeScript also knows whether the `context` object includes user information (it depends on whether your Action uses auth).
The generated types are generic and accept two optional type arguments: `Input` and `Output`.
1. `Input` - The argument (the payload) received by the Action function.
2. `Output` - The Action function's return type.
Use these type arguments to type the Action's inputs and outputs.
Explanation for the example above
The above code says that the Action `createTask` expects an object with the new task's description (its input type is `Pick`) and returns the new task (its output type is `Task`).
On the other hand, the Action `markTaskAsDone` expects an object of type `Pick`. This type is derived from the `Task` entity type.
If you don't care about typing the Action's inputs and outputs, you can omit both type arguments.
TypeScript will then infer the most general types (`never` for the input and `unknown` for the output).
Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you:
- Type support for the arguments and the return value inside the implementation.
- **Full-stack type safety**. We'll explore what this means when we discuss calling the Action from the client.
Read more about type support for implementing Actions in the [API Reference](#implementing-actions).
:::tip Inferring the return type
If don't want to explicitly type the Action's return value, the `satisfies` keyword tells TypeScript to infer it automatically:
```typescript
const createFoo = (async (_args, context) => {
const foo = await context.entities.Foo.create()
return {
newFoo: foo,
message: "Here's your foo!",
returnedAt: new Date(),
}
}) satisfies GetFoo
```
From the snippet above, TypeScript knows:
1. The correct type for `context`.
2. The Action's return type is `{ newFoo: Foo, message: string, returnedAt: Date }`.
If you don't need the context, you can skip specifying the Action's type (and arguments):
```typescript
const createFoo = () => {{ name: 'Foo', date: new Date() }}
```
:::
For a detailed explanation of the Action definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference).
#### Using Actions
##### Using Actions on the client
To call an Action on the client, you can import it from `wasp/client/operations` and call it directly.
The usage doesn't depend on whether the Action is authenticated or not.
Wasp authenticates the logged-in user in the background.
```js
import { createTask, markTaskAsDone } from 'wasp/client/operations'
// ...
const newTask = await createTask({ description: 'Learn TypeScript' })
await markTaskAsDone({ id: 1 })
```
```ts
import { createTask, markTaskAsDone } from 'wasp/client/operations'
// TypeScript automatically infers the return values and type-checks
// the payloads.
const newTask = await createTask({ description: 'Keep learning TypeScript' })
await markTaskAsDone({ id: 1 })
```
Wasp supports **automatic full-stack type safety**.
You only need to specify the Action's type in its server-side definition, and the client code will automatically know its API payload types.
When using Actions on the client, you'll most likely want to use them inside a component:
```jsx title="src/pages/Task.jsx"
import React from 'react'
// highlight-next-line
import { useQuery, getTask, markTaskAsDone } from 'wasp/client/operations'
export const TaskPage = ({ id }) => {
const { data: task } = useQuery(getTask, { id })
if (!task) {
return
"Loading"
}
const { description, isDone } = task
return (
Description:
{description}
Is done:
{isDone ? 'Yes' : 'No'}
{isDone || (
// highlight-next-line
)}
)
}
```
```tsx title="src/pages/Task.tsx"
import React from 'react'
// highlight-next-line
import { useQuery, getTask, markTaskAsDone } from 'wasp/client/operations'
export const TaskPage = ({ id }: { id: number }) => {
const { data: task } = useQuery(getTask, { id })
if (!task) {
return
"Loading"
}
const { description, isDone } = task
return (
Description:
{description}
Is done:
{isDone ? 'Yes' : 'No'}
{isDone || (
// highlight-next-line
)}
)
}
```
Since Actions don't require reactivity, they are safe to use inside components without a hook. Still, Wasp provides comes with the `useAction` hook you can use to enhance actions. Read all about it in the [API Reference](#api-reference).
##### Using Actions on the server
Calling an Action on the server is similar to calling it on the client.
Here's what you have to do differently:
- Import Actions from `wasp/server/operations` instead of `wasp/client/operations`.
- Make sure you pass in a context object with the user to authenticated Actions.
```js
import { createTask, markTaskAsDone } from 'wasp/server/operations'
const user = // Get an AuthUser object, e.g., from context.user
const newTask = await createTask(
{ description: 'Learn TypeScript' },
{ user },
)
await markTaskAsDone({ id: 1 }, { user })
```
```ts
import { createTask, markTaskAsDone } from 'wasp/server/operations'
const user = // Get an AuthUser object, e.g., from context.user
// TypeScript automatically infers the return values and type-checks
// the payloads.
const newTask = await createTask(
{ description: 'Keep learning TypeScript' },
{ user },
)
await markTaskAsDone({ id: 1 }, { user })
```
#### Error Handling
For security reasons, all exceptions thrown in the Action's NodeJS implementation are sent to the client as responses with the HTTP status code `500`, with all other details removed.
Hiding error details by default helps against accidentally leaking possibly sensitive information over the network.
If you do want to pass additional error information to the client, you can construct and throw an appropriate `HttpError` in your implementation:
```js title="src/actions.js"
import { HttpError } from 'wasp/server'
export const createTask = async (args, context) => {
throw new HttpError(
403, // status code
"You can't do this!", // message
{ foo: 'bar' } // data
)
}
```
```ts title="src/actions.ts"
import { type CreateTask } from 'wasp/server/operations'
import { HttpError } from 'wasp/server'
export const createTask: CreateTask = async (args, context) => {
throw new HttpError(
403, // status code
"You can't do this!", // message
{ foo: 'bar' } // data
)
}
```
#### Using Entities in Actions
In most cases, resources used in Actions will be [Entities](../../data-model/entities.md).
To use an Entity in your Action, add it to the `action` declaration in Wasp:
```wasp {4,9} title="main.wasp"
action createTask {
fn: import { createTask } from "@src/actions",
entities: [Task]
}
action markTaskAsDone {
fn: import { markTaskAsDone } from "@src/actions",
entities: [Task]
}
```
```wasp {4,9} title="main.wasp"
action createTask {
fn: import { createTask } from "@src/actions",
entities: [Task]
}
action markTaskAsDone {
fn: import { markTaskAsDone } from "@src/actions",
entities: [Task]
}
```
Wasp will inject the specified Entity into the Action's `context` argument, giving you access to the Entity's Prisma API.
Wasp invalidates frontend Query caches by looking at the Entities used by each Action/Query. Read more about Wasp's smart cache invalidation [here](#cache-invalidation).
```js title="src/actions.js"
// The 'args' object is the payload sent by the caller (most often from the client)
export const createTask = async (args, context) => {
const newTask = await context.entities.Task.create({
data: {
description: args.description,
isDone: false,
},
})
return newTask
}
export const markTaskAsDone = async (args, context) => {
await context.entities.Task.update({
where: { id: args.id },
data: { isDone: true },
})
}
```
```ts title="src/actions.ts"
import { type CreateTask, type MarkTaskAsDone } from 'wasp/server/operations'
import { type Task } from 'wasp/entities'
// The 'args' object is the payload sent by the caller (most often from the client)
export const createTask: CreateTask, Task> = async (
args,
context
) => {
const newTask = await context.entities.Task.create({
data: {
description: args.description,
isDone: false,
},
})
return newTask
}
export const markTaskAsDone: MarkTaskAsDone, void> = async (
args,
context
) => {
await context.entities.Task.update({
where: { id: args.id },
data: { isDone: true },
})
}
```
Again, annotating the Actions is optional, but greatly improves **full-stack type safety**.
The object `context.entities.Task` exposes `prisma.task` from [Prisma's CRUD API](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud).
### Cache Invalidation
One of the trickiest parts of managing a web app's state is making sure the data returned by the Queries is up to date.
Since Wasp uses _react-query_ for Query management, we must make sure to invalidate Queries (more specifically, their cached results managed by _react-query_) whenever they become stale.
It's possible to invalidate the caches manually through several mechanisms _react-query_ provides (e.g., refetch, direct invalidation).
However, since manual cache invalidation quickly becomes complex and error-prone, Wasp offers a faster and a more effective solution to get you started: **automatic Entity-based Query cache invalidation**.
Because Actions can (and most often do) modify the state while Queries read it, Wasp invalidates a Query's cache whenever an Action that uses the same Entity is executed.
For example, if the Action `createTask` and Query `getTasks` both use the Entity `Task`, executing `createTask` may cause the cached result of `getTasks` to become outdated. In response, Wasp will invalidate it, causing `getTasks` to refetch data from the server and update it.
In practice, this means that Wasp keeps the Queries "fresh" without requiring you to think about cache invalidation.
On the other hand, this kind of automatic cache invalidation can become wasteful (some updates might not be necessary) and will only work for Entities. If that's an issue, you can use the mechanisms provided by _react-query_ for now, and expect more direct support in Wasp for handling those use cases in a nice, elegant way.
If you wish to optimistically set cache values after performing an Action, you can do so using [optimistic updates](https://stackoverflow.com/a/33009713). Configure them using Wasp's [useAction hook](#the-useaction-hook-and-optimistic-updates). This is currently the only manual cache invalidation mechanism Wasps supports natively. For everything else, you can always rely on _react-query_.
### Differences Between Queries and Actions
Actions and Queries are two closely related concepts in Wasp. They might seem to perform similar tasks, but Wasp treats them differently, and each concept represents a different thing.
Here are the key differences between Queries and Actions:
1. Actions can (and often should) modify the server's state, while Queries are only permitted to read it. Wasp relies on you adhering to this convention when performing cache invalidations, so it's crucial to follow it.
2. Actions don't need to be reactive, so you can call them directly. However, Wasp does provide a [`useAction` React hook](#the-useaction-hook-and-optimistic-updates) for adding extra behavior to the Action (like optimistic updates).
3. `action` declarations in Wasp are mostly identical to `query` declarations. The only difference lies in the declaration's name.
### API Reference
#### Declaring Actions in Wasp
The `action` declaration supports the following fields:
- `fn: ExtImport` Required!
The import statement of the Action's NodeJs implementation.
- `entities: [Entity]`
A list of entities you wish to use inside your Action.
For instructions on using Entities in Actions, take a look at [the guide](#using-entities-in-actions).
##### Example
Declaring the Action:
```wasp
query createFoo {
fn: import { createFoo } from "@src/actions"
entities: [Foo]
}
```
Enables you to import and use it anywhere in your code (on the server or the client):
```js
// Use it on the client
import { createFoo } from 'wasp/client/operations'
// Use it on the server
import { createFoo } from 'wasp/server/operations'
```
Declaring the Action:
```wasp
query createFoo {
fn: import { createFoo } from "@src/actions"
entities: [Foo]
}
```
Enables you to import and use it anywhere in your code (on the server or the client):
```ts
// Use it on the client
import { createFoo } from 'wasp/client/operations'
// Use it on the server
import { createFoo } from 'wasp/server/operations'
```
As well as the following type import on the server:
```ts
import { type CreateFoo } from 'wasp/server/operations'
```
#### Implementing Actions
The Action's implementation is a NodeJS function that takes two arguments (it can be an `async` function if you need to use the `await` keyword).
Since both arguments are positional, you can name the parameters however you want, but we'll stick with `args` and `context`:
1. `args` (type depends on the Action)
An object containing the data **passed in when calling the Action** (e.g., filtering conditions).
Check [the usage examples](#using-actions) to see how to pass this object to the Action.
2. `context` (type depends on the Action)
An additional context object **passed into the Action by Wasp**. This object contains user session information, as well as information about entities. Check the [section about using entities in Actions](#using-entities-in-actions) to see how to use the entities field on the `context` object, or the [auth section](../../auth/overview#using-the-contextuser-object) to see how to use the `user` object.
After you [declare the Action](#declaring-actions), Wasp generates a generic type you can use when defining its implementation.
For the Action declared as `createSomething`, the generated type is called `CreateSomething`:
```ts
import { type CreateSomething } from 'wasp/server/operations'
```
It expects two (optional) type arguments:
1. `Input`
The type of the `args` object (the Action's input payload). The default value is `never`.
2. `Output`
The type of the Action's return value (the Action's output payload). The default value is `unknown`.
The defaults were chosen to make the type signature as permissive as possible. If don't want your Action to take/return anything, use `void` as a type argument.
##### Example
The following Action:
```wasp
action createFoo {
fn: import { createFoo } from "@src/actions"
entities: [Foo]
}
```
Expects to find a named export `createfoo` from the file `src/actions.js`
```js title="actions.js"
export const createFoo = (args, context) => {
// implementation
}
```
The following Action:
```wasp
action createFoo {
fn: import { createFoo } from "@src/actions"
entities: [Foo]
}
```
Expects to find a named export `createfoo` from the file `src/actions.js`
You can use the generated type `CreateFoo` and specify the Action's inputs and outputs using its type arguments.
```ts title="actions.ts"
import { type CreateFoo } from 'wasp/server/operations'
type Foo = // ...
export const createFoo: CreateFoo<{ bar: string }, Foo> = (args, context) => {
// implementation
};
```
In this case, the Action expects to receive an object with a `bar` field of type `string` (this is the type of `args`), and return a value of type `Foo` (this must match the type of the Action's return value).
#### The `useAction` Hook and Optimistic Updates
Make sure you understand how [Queries](../../data-model/operations/queries.md) and [Cache Invalidation](#cache-invalidation) work before reading this chapter.
When using Actions in components, you can enhance them with the help of the `useAction` hook. This hook comes bundled with Wasp, and is used for decorating Wasp Actions.
In other words, the hook returns a function whose API matches the original Action while also doing something extra under the hood (depending on how you configure it).
The `useAction` hook accepts two arguments:
- `actionFn` Required!
The Wasp Action (the client-side Action function generated by Wasp based on a Action declaration) you wish to enhance.
- `actionOptions`
An object configuring the extra features you want to add to the given Action. While this argument is technically optional, there is no point in using the `useAction` hook without providing it (it would be the same as using the Action directly). The Action options object supports the following fields:
- `optimisticUpdates`
An array of objects where each object defines an [optimistic update](https://stackoverflow.com/a/33009713) to perform on the Query cache. To define an optimistic update, you must specify the following properties:
- `getQuerySpecifier` Required!
A function returning the Query specifier (a value used to address the Query you want to update). A Query specifier is an array specifying the query function and arguments. For example, to optimistically update the Query used with `useQuery(fetchFilteredTasks, {isDone: true }]`, your `getQuerySpecifier` function would have to return the array `[fetchFilteredTasks, { isDone: true}]`. Wasp will forward the argument you pass into the decorated Action to this function (you can use the properties of the added/changed item to address the Query).
- `updateQuery` Required!
The function used to perform the optimistic update. It should return the desired state of the cache. Wasp will call it with the following arguments:
- `item` - The argument you pass into the decorated Action.
- `oldData` - The currently cached value for the Query identified by the specifier.
:::caution
The `updateQuery` function must be a pure function. It must return the desired cache value identified by the `getQuerySpecifier` function and _must not_ perform any side effects.
Also, make sure you only update the Query caches affected by your Action causing the optimistic update (Wasp cannot yet verify this).
Finally, your implementation of the `updateQuery` function should work correctly regardless of the state of `oldData` (e.g., don't rely on array positioning). If you need to do something else during your optimistic update, you can directly use _react-query_'s lower-level API (read more about it [here](#advanced-usage)).
:::
Here's an example showing how to configure the Action `markTaskAsDone` that toggles a task's `isDone` status to perform an optimistic update:
```jsx title="src/pages/Task.jsx"
import React from 'react'
import {
useQuery,
useAction,
getTask,
markTaskAsDone,
} from 'wasp/client/operations'
const TaskPage = ({ id }) => {
const { data: task } = useQuery(getTask, { id })
// highlight-start
const markTaskAsDoneOptimistically = useAction(markTaskAsDone, {
optimisticUpdates: [
{
getQuerySpecifier: ({ id }) => [getTask, { id }],
updateQuery: (_payload, oldData) => ({ ...oldData, isDone: true }),
},
],
})
// highlight-end
if (!task) {
return
"Loading"
}
const { description, isDone } = task
return (
Description:
{description}
Is done:
{isDone ? 'Yes' : 'No'}
{isDone || (
)}
)
}
export default TaskPage
```
```tsx title="src/pages/Task.tsx"
import React from 'react'
import {
useQuery,
useAction,
type OptimisticUpdateDefinition,
getTask,
markTaskAsDone,
} from 'wasp/client/operations'
type TaskPayload = Pick;
const TaskPage = ({ id }: { id: number }) => {
const { data: task } = useQuery(getTask, { id });
// Typescript automatically type-checks the payload type.
// highlight-start
const markTaskAsDoneOptimistically = useAction(markTaskAsDone, {
optimisticUpdates: [
{
getQuerySpecifier: ({ id }) => [getTask, { id }],
updateQuery: (_payload, oldData) => ({ ...oldData, isDone: true }),
} as OptimisticUpdateDefinition,
],
});
// highlight-end
if (!task) {
return
);
};
export default TaskPage;
```
##### Advanced usage
The `useAction` hook currently only supports specifying optimistic updates. You can expect more features in future versions of Wasp.
Wasp's optimistic update API is deliberately small and focuses exclusively on updating Query caches (as that's the most common use case). You might need an API that offers more options or a higher level of control. If that's the case, instead of using Wasp's `useAction` hook, you can use _react-query_'s `useMutation` hook and directly work with [their low-level API](https://tanstack.com/query/v4/docs/framework/react/guides/optimistic-updates).
If you decide to use _react-query_'s API directly, you will need access to Query cache key. Wasp internally uses this key but abstracts it from the programmer. Still, you can easily obtain it by accessing the `queryCacheKey` property on any Query:
```js
import { getTasks } from 'wasp/client/operations'
const queryKey = getTasks.queryCacheKey
```
```ts
import { getTasks } from 'wasp/client/operations'
const queryKey = getTasks.queryCacheKey
```
## Automatic CRUD
If you have a lot of experience writing full-stack apps, you probably ended up doing some of the same things many times: listing data, adding data, editing it, and deleting it.
Wasp makes handling these boring bits easy by offering a higher-level concept called Automatic CRUD.
With a single declaration, you can tell Wasp to automatically generate server-side logic (i.e., Queries and Actions) for creating, reading, updating and deleting [Entities](../data-model/entities). As you update definitions for your Entities, Wasp automatically regenerates the backend logic.
:::caution Early preview
This feature is currently in early preview and we are actively working on it. Read more about [our plans](#future-of-crud-operations-in-wasp) for CRUD operations.
:::
### Overview
Imagine we have a `Task` entity and we want to enable CRUD operations for it:
```prisma title="schema.prisma"
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean
}
```
We can then define a new `crud` called `Tasks`.
We specify to use the `Task` entity and we enable the `getAll`, `get`, `create` and `update` operations (let's say we don't need the `delete` operation).
```wasp title="main.wasp"
crud Tasks {
entity: Task,
operations: {
getAll: {
isPublic: true, // by default only logged in users can perform operations
},
get: {},
create: {
overrideFn: import { createTask } from "@src/tasks",
},
update: {},
},
}
```
1. It uses default implementation for `getAll`, `get`, and `update`,
2. ... while specifying a custom implementation for `create`.
3. `getAll` will be public (no auth needed), while the rest of the operations will be private.
Here's what it looks like when visualized:
We can now use the CRUD queries and actions we just specified in our client code.
Keep reading for an example of Automatic CRUD in action, or skip ahead for the [API Reference](#api-reference).
### Example: A Simple TODO App
Let's create a full-app example that uses automatic CRUD. We'll stick to using the `Task` entity from the previous example, but we'll add a `User` entity and enable [username and password](../auth/username-and-pass) based auth.
#### Creating the App
We can start by running `wasp new tasksCrudApp` and then adding the following to the `main.wasp` file:
```wasp title="main.wasp"
app tasksCrudApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "Tasks Crud App",
// We enabled auth and set the auth method to username and password
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login",
},
}
// Tasks app routes
route RootRoute { path: "/", to: MainPage }
page MainPage {
component: import { MainPage } from "@src/MainPage",
authRequired: true,
}
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@src/LoginPage",
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@src/SignupPage",
}
```
And let's define our entities in the `schema.prisma` file:
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
tasks Task[]
}
// We defined a Task entity on which we'll enable CRUD later on
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean
userId Int
user User @relation(fields: [userId], references: [id])
}
```
We can then run `wasp db migrate-dev` to create the database and run the migrations.
#### Adding CRUD to the `Task` Entity â¨
Let's add the following `crud` declaration to our `main.wasp` file:
```wasp title="main.wasp"
// ...
crud Tasks {
entity: Task,
operations: {
getAll: {},
create: {
overrideFn: import { createTask } from "@src/tasks",
},
},
}
```
You'll notice that we enabled only `getAll` and `create` operations. This means that only these operations will be available.
We also overrode the `create` operation with a custom implementation. This means that the `create` operation will not be generated, but instead, the `createTask` function from `@src/tasks.{js,ts}` will be used.
#### Our Custom `create` Operation
We need a custom `create` operation because we want to make sure that the task is connected to the user creating it.
Automatic CRUD doesn't yet support this by default.
Read more about the default implementations [here](#declaring-a-crud-with-default-options).
Here's the `src/tasks.{js,ts}` file:
```js title="src/tasks.js"
import { HttpError } from 'wasp/server'
export const createTask = async (args, context) => {
if (!context.user) {
throw new HttpError(401, 'User not authenticated.')
}
const { description, isDone } = args
const { Task } = context.entities
return await Task.create({
data: {
description,
isDone,
// highlight-start
// Connect the task to the user that is creating it
user: {
connect: {
id: context.user.id,
},
},
// highlight-end
},
})
}
```
```ts title="src/tasks.ts"
import { type Tasks } from 'wasp/server/crud'
import { type Task } from 'wasp/entities'
import { HttpError } from 'wasp/server'
type CreateTaskInput = { description: string; isDone: boolean }
export const createTask: Tasks.CreateAction = async (
args,
context
) => {
if (!context.user) {
throw new HttpError(401, 'User not authenticated.')
}
const { description, isDone } = args
const { Task } = context.entities
return await Task.create({
data: {
description,
isDone,
// highlight-start
// Connect the task to the user that is creating it
user: {
connect: {
id: context.user.id,
},
},
// highlight-end
},
})
}
```
Wasp automatically generates the `Tasks.CreateAction` type based on the CRUD declaration in your Wasp file.
Use it to type the CRUD action's implementation.
The `Tasks.CreateAction` type works exactly like the types Wasp generates for [Queries](../data-model/operations/queries#type-support-for-queries) and [Actions](../data-model/operations/actions#type-support-for-actions).
In other words, annotating the action with `Tasks.CreateAction` tells TypeScript about the type of the Action's `context` object, while the two type arguments allow you to specify the Action's inputs and outputs.
Read more about type support for CRUD overrides in the [API reference](#defining-the-overrides).
#### Using the Generated CRUD Operations on the Client
And let's use the generated operations in our client code:
```jsx title="src/MainPage.jsx"
// highlight-next-line
import { Tasks } from 'wasp/client/crud'
import { useState } from 'react'
export const MainPage = () => {
// highlight-next-line
const { data: tasks, isLoading, error } = Tasks.getAll.useQuery()
// highlight-next-line
const createTask = Tasks.create.useAction()
const [taskDescription, setTaskDescription] = useState('')
function handleCreateTask() {
createTask({ description: taskDescription, isDone: false })
setTaskDescription('')
}
if (isLoading) return
Loading...
if (error) return
Error: {error.message}
return (
setTaskDescription(e.target.value)}
/>
{tasks.map((task) => (
{task.description}
))}
)
}
```
```tsx title="src/MainPage.tsx"
// highlight-next-line
import { Tasks } from 'wasp/client/crud'
import { useState } from 'react'
export const MainPage = () => {
// highlight-next-line
// Thanks to full-stack type safety, all payload types are inferred
// highlight-next-line
// automatically
// highlight-next-line
const { data: tasks, isLoading, error } = Tasks.getAll.useQuery()
// highlight-next-line
const createTask = Tasks.create.useAction()
const [taskDescription, setTaskDescription] = useState('')
function handleCreateTask() {
createTask({ description: taskDescription, isDone: false })
setTaskDescription('')
}
if (isLoading) return
Loading...
if (error) return
Error: {error.message}
return (
setTaskDescription(e.target.value)}
/>
{tasks.map((task) => (
{task.description}
))}
)
}
```
And here are the login and signup pages, where we are using Wasp's [Auth UI](../auth/ui) components:
```jsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
import { Link } from 'react-router'
export function LoginPage() {
return (
Create an account
)
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
import { Link } from 'react-router'
export function LoginPage() {
return (
Create an account
)
}
```
```jsx title="src/SignupPage.jsx"
import { SignupForm } from 'wasp/client/auth'
export function SignupPage() {
return (
)
}
```
```tsx title="src/SignupPage.tsx"
import { SignupForm } from 'wasp/client/auth'
export function SignupPage() {
return (
)
}
```
That's it. You can now run `wasp start` and see the app in action. âĄī¸
You should see a login page and a signup page. After you log in, you should see a page with a list of tasks and a form to create new tasks.
### Future of CRUD Operations in Wasp
CRUD operations currently have a limited set of knowledge about the business logic they are implementing.
- For example, they don't know that a task should be connected to the user that is creating it. This is why we had to override the `create` operation in the example above.
- Another thing: they are not aware of the authorization rules. For example, they don't know that a user should not be able to create a task for another user. In the future, we will be adding role-based authorization to Wasp, and we plan to make CRUD operations aware of the authorization rules.
- Another issue is input validation and sanitization. For example, we might want to make sure that the task description is not empty.
CRUD operations are a mechanism for getting a backend up and running quickly, but it depends on the information it can get from the Wasp app. The more information that it can pick up from your app, the more powerful it will be out of the box.
We plan on supporting CRUD operations and growing them to become the easiest way to create your backend. Follow along on [this GitHub issue](https://github.com/wasp-lang/wasp/issues/1253) to see how we are doing.
### API Reference
CRUD declaration works on top of an existing entity declaration. We'll fully explore the API using two examples:
1. A basic CRUD declaration that relies on default options.
2. A more involved CRUD declaration that uses extra options and overrides.
#### Declaring a CRUD With Default Options
If we create CRUD operations for an entity named `Task`, like this:
```wasp title="main.wasp"
crud Tasks { // crud name here is "Tasks"
entity: Task,
operations: {
get: {},
getAll: {},
create: {},
update: {},
delete: {},
},
}
```
Wasp will give you the following default implementations:
**get** - returns one entity based on the `id` field
```js
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.findUnique({ where: { id: args.id } })
```
**getAll** - returns all entities
```js
// ...
// If the operation is not public, Wasp checks if an authenticated user
// is making the request.
return Task.findMany()
```
**create** - creates a new entity
```js
// ...
return Task.create({ data: args.data })
```
**update** - updates an existing entity
```js
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.update({ where: { id: args.id }, data: args.data })
```
**delete** - deletes an existing entity
```js
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.delete({ where: { id: args.id } })
```
```wasp title="main.wasp"
crud Tasks { // crud name here is "Tasks"
entity: Task,
operations: {
get: {},
getAll: {},
create: {},
update: {},
delete: {},
},
}
```
Wasp will give you the following default implementations:
**get** - returns one entity based on the `id` field
```ts
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.findUnique({ where: { id: args.id } })
```
**getAll** - returns all entities
```ts
// ...
// If the operation is not public, Wasp checks if an authenticated user
// is making the request.
return Task.findMany()
```
**create** - creates a new entity
```ts
// ...
return Task.create({ data: args.data })
```
**update** - updates an existing entity
```ts
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.update({ where: { id: args.id }, data: args.data })
```
**delete** - deletes an existing entity
```ts
// ...
// Wasp uses the field marked with `@id` in Prisma schema as the id field.
return Task.delete({ where: { id: args.id } })
```
:::info Current Limitations
In the default `create` and `update` implementations, we are saving all of the data that the client sends to the server. This is not always desirable, i.e. in the case when the client should not be able to modify all of the data in the entity.
[In the future](#future-of-crud-operations-in-wasp), we are planning to add validation of action input, where only the data that the user is allowed to change will be saved.
For now, the solution is to provide an override function. You can override the default implementation by using the `overrideFn` option and implementing the validation logic yourself.
:::
#### Declaring a CRUD With All Available Options
Here's an example of a more complex CRUD declaration:
```wasp title="main.wasp"
crud Tasks { // crud name here is "Tasks"
entity: Task,
operations: {
getAll: {
isPublic: true, // optional, defaults to false
},
get: {},
create: {
overrideFn: import { createTask } from "@src/tasks", // optional
},
update: {},
},
}
```
```wasp title="main.wasp"
crud Tasks { // crud name here is "Tasks"
entity: Task,
operations: {
getAll: {
isPublic: true, // optional, defaults to false
},
get: {},
create: {
overrideFn: import { createTask } from "@src/tasks", // optional
},
update: {},
},
}
```
The CRUD declaration features the following fields:
- `entity: Entity` Required!
The entity to which the CRUD operations will be applied.
- `operations: { [operationName]: CrudOperationOptions }` Required!
The operations to be generated. The key is the name of the operation, and the value is the operation configuration.
- The possible values for `operationName` are:
- `getAll`
- `get`
- `create`
- `update`
- `delete`
- `CrudOperationOptions` can have the following fields:
- `isPublic: bool` - Whether the operation is public or not. If it is public, no auth is required to access it. If it is not public, it will be available only to authenticated users. Defaults to `false`.
- `overrideFn: ExtImport` - The import statement of the optional override implementation in Node.js.
##### Defining the overrides
Like with actions and queries, you can define the implementation in a Javascript/Typescript file. The overrides are functions that take the following arguments:
- `args`
The arguments of the operation i.e. the data sent from the client.
- `context`
Context contains the `user` making the request and the `entities` object with the entity that's being operated on.
You can also import types for each of the functions you want to override by importing the `{crud name}` from `wasp/server/crud`. The available types are:
- `{crud name}.GetAllQuery`
- `{crud name}.GetQuery`
- `{crud name}.CreateAction`
- `{crud name}.UpdateAction`
- `{crud name}.DeleteAction`
If you have a CRUD named `Tasks`, you would import the types like this:
```ts
import { type Tasks } from 'wasp/server/crud'
// Each of the types is a generic type, so you can use it like this:
export const getAllOverride: Tasks.GetAllQuery = async (
args,
context
) => {
// ...
}
```
For a usage example, check the [example guide](../data-model/crud#adding-crud-to-the-task-entity-).
##### Using the CRUD operations in client code
On the client, you import the CRUD operations from `wasp/client/crud` by import the `{crud name}` object. For example, if you have a CRUD called `Tasks`, you would import the operations like this:
```jsx title="SomePage.jsx"
import { Tasks } from 'wasp/client/crud'
```
```tsx title="SomePage.tsx"
import { Tasks } from 'wasp/client/crud'
```
You can then access the operations like this:
```jsx title="SomePage.jsx"
const { data } = Tasks.getAll.useQuery()
const { data } = Tasks.get.useQuery({ id: 1 })
const createAction = Tasks.create.useAction()
const updateAction = Tasks.update.useAction()
const deleteAction = Tasks.delete.useAction()
```
```tsx title="SomePage.tsx"
const { data } = Tasks.getAll.useQuery()
const { data } = Tasks.get.useQuery({ id: 1 })
const createAction = Tasks.create.useAction()
const updateAction = Tasks.update.useAction()
const deleteAction = Tasks.delete.useAction()
```
All CRUD operations are implemented with [Queries and Actions](../data-model/operations/overview) under the hood, which means they come with all the features you'd expect (e.g., automatic SuperJSON serialization, full-stack type safety when using TypeScript)
---
Join our **community** on [Discord](https://discord.com/invite/rzdnErX), where we chat about full-stack web stuff. Join us to see what we are up to, share your opinions or get help with CRUD operations.
## Databases
[Entities](../data-model/entities.md), [Operations](../data-model/operations/overview) and [Automatic CRUD](../data-model/crud.md) together make a high-level interface for working with your app's data. Still, all that data has to live somewhere, so let's see how Wasp deals with databases.
### Supported Database Backends
Wasp supports multiple database backends. We'll list and explain each one.
#### SQLite
The default database Wasp uses is [SQLite](https://www.sqlite.org/index.html).
When you create a new Wasp project, the `schema.prisma` file will have SQLite as the default database provider:
```prisma title="schema.prisma"
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// ...
```
Read more about how Wasp uses the Prisma schema file in the [Prisma schema file](./prisma-file.md) section.
When you use the SQLite database, Wasp sets the `DATABASE_URL` environment variable for you.
SQLite is a great way to get started with a new project because it doesn't require any configuration, but Wasp can only use it in development. Once you want to deploy your Wasp app to production, you'll need to switch to PostgreSQL and stick with it.
Fortunately, migrating from SQLite to PostgreSQL is pretty simple, and we have [a guide](#migrating-from-sqlite-to-postgresql) to help you.
#### PostgreSQL
[PostgreSQL](https://www.postgresql.org/) is the most advanced open-source database and one of the most popular databases overall.
It's been in active development for 20+ years.
Therefore, if you're looking for a battle-tested database, look no further.
To use PostgreSQL with Wasp, set the provider to `"postgresql"` in the `schema.prisma` file:
```prisma title="schema.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ...
```
Read more about how Wasp uses the Prisma schema file in the [Prisma schema file](./prisma-file.md) section.
You'll have to ensure a database instance is running during development to use PostgreSQL. Wasp needs access to your database for commands such as `wasp start` or `wasp db migrate-dev`.
We cover all supported ways of connecting to a database in [the next section](#connecting-to-a-database).
### Connecting to a Database
#### SQLite
If you are using SQLite, you don't need to do anything special to connect to the database. Wasp will take care of it for you.
#### PostgreSQL
If you are using PostgreSQL, Wasp supports two ways of connecting to a database:
1. For managed experience, let Wasp spin up a ready-to-go development database for you.
2. For more control, you can specify a database URL and connect to an existing database that you provisioned yourself.
##### Using the Dev Database provided by Wasp
The command `wasp start db` will start a default PostgreSQL dev database for you.
Your Wasp app will automatically connect to it, just keep `wasp start db` running in the background.
Also, make sure that:
- You have [Docker installed](https://www.docker.com/get-started/) and it's available in your `PATH`.
- The port `5432` isn't taken.
:::tip
In case you might want to connect to the dev database through the external tool like `psql` or [pgAdmin](https://www.pgadmin.org/), the credentials are printed in the console when you run `wasp db start`, at the very beginning.
:::
###### Customising the dev database {#custom-database}
The Wasp development database uses the [PostgreSQL 18 Docker image](https://hub.docker.com/_/postgres/tags?name=18) by default, and will set up its data volumes according to their guidance.
If you need to customise the development database, you can use the following options:
- `--db-image`: Specify a custom Docker image
Useful for PostgreSQL extensions or specific versions (for example, PostGIS, pgvector, etc.).
- `--db-volume-mount-path`: Specify the volume mount path inside the container
You only need to set this option if your custom `--db-image` is based on **PostgreSQL 17 or older** (check the `postgres:15` example below).
If the volume mount path is incorrect, the data won't be persisted in your development database.
Here are some examples of customising the development database:
```bash
## Use default PostgreSQL image:
wasp start db
## Same as:
wasp start db --db-image postgres:18
## Use PostgreSQL with PostGIS extension for geographic data:
wasp start db --db-image postgis/postgis:18-3.6
## Use PostgreSQL with pgvector extension for AI embeddings:
wasp start db --db-image pgvector/pgvector:pg18
## Use PostgreSQL version 15 (requires different volume path):
wasp start db --db-image postgres:15 --db-volume-mount-path /var/lib/postgresql/data
```
:::note
The custom Docker image you specify must use the `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` environment variables when configuring the database. Wasp will use those values when connecting to the database. We recommend basing your image on the official [PostgreSQL Docker image](https://hub.docker.com/_/postgres), as it automatically uses these environment variables to set up the database name, user, and password.
:::
##### Connecting to an existing database
If you want to spin up your own dev database (or connect to an external one), you can tell Wasp about it using the `DATABASE_URL` environment variable. Wasp will use the value of `DATABASE_URL` as a connection string.
The easiest way to set the necessary `DATABASE_URL` environment variable is by adding it to the [.env.server](../project/env-vars) file in the root dir of your Wasp project (if that file doesn't yet exist, create it):
```env title=".env.server"
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
```
Alternatively, you can set it inline when running `wasp` (this applies to all environment variables):
```bash
DATABASE_URL= wasp ...
```
This trick is useful for running a certain `wasp` command on a specific database.
For example, you could do:
```bash
DATABASE_URL= wasp db seed myProductionSeed
```
This command seeds the data for a fresh staging or production database. Read more about [seeding the database](#seeding-the-database).
### Migrating from SQLite to PostgreSQL
To run your Wasp app in production, you'll need to switch from SQLite to PostgreSQL.
1. Set the provider to `"postgresql"` in the `schema.prisma` file:
```prisma title="schema.prisma"
datasource db {
// highlight-next-line
provider = "postgresql"
url = env("DATABASE_URL")
}
// ...
```
2. Delete all the old migrations, since they are SQLite migrations and can't be used with PostgreSQL, as well as the SQLite database by running [`wasp clean`](../general/cli#project-commands):
```bash
rm -r migrations/
wasp clean
```
3. Ensure your new database is running (check the [section on connecting to a database](#connecting-to-a-database) to see how). Leave it running, since we need it for the next step.
4. In a different terminal, run `wasp db migrate-dev` to apply the changes and create a new initial migration.
5. That is it, you are all done!
### Seeding the Database
**Database seeding** is a term used for populating the database with some initial data.
Seeding is most commonly used for:
1. Getting the development database into a state convenient for working and testing.
2. Initializing any database (`dev`, `staging`, or `prod`) with essential data it requires to operate.
For example, populating the Currency table with default currencies, or the Country table with all available countries.
#### Writing a Seed Function
You can define as many **seed functions** as you want in an array under the `app.db.seeds` field:
```wasp title="main.wasp"
app MyApp {
// ...
db: {
seeds: [
import { devSeedSimple } from "@src/dbSeeds.js",
import { prodSeed } from "@src/dbSeeds.js"
]
}
}
```
```wasp title="main.wasp"
app MyApp {
// ...
db: {
seeds: [
import { devSeedSimple } from "@src/dbSeeds.js",
import { prodSeed } from "@src/dbSeeds.js"
]
}
}
```
Each seed function must be an async function that takes one argument, `prisma`, which is a [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud) instance used to interact with the database.
This is the same Prisma Client instance that Wasp uses internally.
Since a seed function falls under server-side code, it can import other server-side functions. This is convenient because you might want to seed the database using Actions.
Here's an example of a seed function that imports an Action:
```js
import { createTask } from './actions.js'
import { sanitizeAndSerializeProviderData } from 'wasp/server/auth'
export const devSeedSimple = async (prisma) => {
const user = await createUser(prisma, {
username: 'RiuTheDog',
password: 'bark1234',
})
await createTask(
{ description: 'Chase the cat' },
{ user, entities: { Task: prisma.task } }
)
}
async function createUser(prisma, data) {
const newUser = await prisma.user.create({
data: {
auth: {
create: {
identities: {
create: {
providerName: 'username',
providerUserId: data.username,
providerData: await sanitizeAndSerializeProviderData({
hashedPassword: data.password
}),
},
},
},
},
},
})
return newUser
}
```
```ts
import { createTask } from './actions.js'
import type { DbSeedFn } from 'wasp/server'
import { sanitizeAndSerializeProviderData } from 'wasp/server/auth'
import type { AuthUser } from 'wasp/auth'
import type { PrismaClient } from 'wasp/server'
export const devSeedSimple: DbSeedFn = async (prisma) => {
const user = await createUser(prisma, {
username: 'RiuTheDog',
password: 'bark1234',
})
await createTask(
{ description: 'Chase the cat', isDone: false },
{ user, entities: { Task: prisma.task } }
)
};
async function createUser(
prisma: PrismaClient,
data: { username: string, password: string }
): Promise {
const newUser = await prisma.user.create({
data: {
auth: {
create: {
identities: {
create: {
providerName: 'username',
providerUserId: data.username,
providerData: await sanitizeAndSerializeProviderData<'username'>({
hashedPassword: data.password
}),
},
},
},
},
},
})
return newUser
}
```
Wasp exports a type called `DbSeedFn` which you can use to easily type your seeding function.
Wasp defines `DbSeedFn` like this:
```typescript
type DbSeedFn = (prisma: PrismaClient) => Promise
```
Annotating the function `devSeedSimple` with this type tells TypeScript:
- The seeding function's argument (`prisma`) is of type `PrismaClient`.
- The seeding function's return value is `Promise`.
#### Running seed functions
Run the command `wasp db seed` and Wasp will ask you which seed function you'd like to run (if you've defined more than one).
Alternatively, run the command `wasp db seed ` to choose a specific seed function right away, for example:
```
wasp db seed devSeedSimple
```
Check the [API Reference](#cli-commands-for-seeding-the-database) for more details on these commands.
:::tip
You'll often want to call `wasp db seed` right after you run `wasp db reset`, as it makes sense to fill the database with initial data after clearing it.
:::
### Customising the Prisma Client
Wasp interacts with the database using the [Prisma Client](https://www.prisma.io/docs/orm/prisma-client).
To customize the client, define a function in the `app.db.prismaSetupFn` field that returns a Prisma Client instance.
This allows you to configure features like [logging](https://www.prisma.io/docs/orm/prisma-client/observability-and-logging/logging) or [client extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions):
```wasp title=main.wasp
app MyApp {
title: "My app",
// ...
db: {
prismaSetupFn: import { setUpPrisma } from "@src/prisma"
}
}
```
```js title="src/prisma.js"
import { PrismaClient } from '@prisma/client'
export const setUpPrisma = () => {
const prisma = new PrismaClient({
log: ['query'],
}).$extends({
query: {
task: {
async findMany({ args, query }) {
args.where = {
...args.where,
description: { not: { contains: 'hidden by setUpPrisma' } },
}
return query(args)
},
},
},
})
return prisma
}
```
```wasp title=main.wasp
app MyApp {
title: "My app",
// ...
db: {
prismaSetupFn: import { setUpPrisma } from "@src/prisma"
}
}
```
```ts title="src/prisma.ts"
import { PrismaClient } from '@prisma/client'
export const setUpPrisma = () => {
const prisma = new PrismaClient({
log: ['query'],
}).$extends({
query: {
task: {
async findMany({ args, query }) {
args.where = {
...args.where,
description: { not: { contains: 'hidden by setUpPrisma' } },
}
return query(args)
},
},
},
})
return prisma
}
```
### API Reference
```wasp title="main.wasp"
app MyApp {
title: "My app",
// ...
db: {
seeds: [
import devSeed from "@src/dbSeeds"
],
prismaSetupFn: import { setUpPrisma } from "@src/prisma"
}
}
```
```wasp title="main.wasp"
app MyApp {
title: "My app",
// ...
db: {
seeds: [
import devSeed from "@src/dbSeeds"
],
prismaSetupFn: import { setUpPrisma } from "@src/prisma"
}
}
```
`app.db` is a dictionary with the following fields (all fields are optional):
- `seeds: [ExtImport]`
Defines the seed functions you can use with the `wasp db seed` command to seed your database with initial data.
Read the [Seeding section](#seeding-the-database) for more details.
- `prismaSetupFn: ExtImport`
Defines a function that sets up the Prisma Client instance. Wasp expects it to return a Prisma Client instance.
You can use this function to set up [logging](https://www.prisma.io/docs/orm/prisma-client/observability-and-logging/logging) or [client extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions):
```ts title="src/prisma.ts"
import { PrismaClient } from '@prisma/client'
export const setUpPrisma = () => {
const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
})
return prisma
}
```
#### CLI Commands for Seeding the Database
Use one of the following commands to run the seed functions:
- `wasp db seed`
If you've only defined a single seed function, this command runs it. If you've defined multiple seed functions, it asks you to choose one interactively.
- `wasp db seed `
This command runs the seed function with the specified name. The name is the identifier used in its `import` expression in the `app.db.seeds` list.
For example, to run the seed function `devSeedSimple` which was defined like this:
```wasp title="main.wasp"
app MyApp {
// ...
db: {
seeds: [
// ...
import { devSeedSimple } from "@src/dbSeeds.js",
]
}
}
```
```wasp title="main.wasp"
app MyApp {
// ...
db: {
seeds: [
// ...
import { devSeedSimple } from "@src/dbSeeds.js",
]
}
}
```
Use the following command:
```
wasp db seed devSeedSimple
```
## Prisma Schema File
Wasp uses [Prisma](https://www.prisma.io/) to interact with the database. Prisma is a "Next-generation Node.js and TypeScript ORM" that provides a type-safe API for working with your database.
With Prisma, you define your application's data model in a `schema.prisma` file. Read more about how Wasp Entities relate to Prisma models on the [Entities](./entities.md) page.
In Wasp, the `schema.prisma` file is located in your project's root directory:
```c
.
âââ main.wasp
...
// highlight-next-line
âââ schema.prisma
âââ src
âââ tsconfig.json
âââ vite.config.ts
```
Wasp uses the `schema.prisma` file to understand your app's data model and generate the necessary code to interact with the database.
### Wasp file and Prisma schema file
Let's see how Wasp and Prisma files work together to define your application.
Here's an example `schema.prisma` file where we defined some database options and two models (User and Task) with a one-to-many relationship:
```prisma title="schema.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
}
```
Wasp reads this `schema.prisma` file and extracts the info about your database models and database config.
The `datasource` block defines which database you want to use (PostgreSQL in this case) and some other options.
The `generator` block defines how to generate the Prisma Client code that you can use in your application to interact with the database.
Finally, Prisma models become Wasp Entities which can be then used in the `main.wasp` file:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
}
...
// Using Wasp Entities in the Wasp file
query getTasks {
fn: import { getTasks } from "@src/queries",
// highlight-next-line
entities: [Task]
}
job myJob {
executor: PgBoss,
perform: {
fn: import { foo } from "@src/workers/bar"
},
// highlight-next-line
entities: [Task],
}
api fooBar {
fn: import { fooBar } from "@src/apis",
// highlight-next-line
entities: [Task],
httpRoute: (GET, "/foo/bar/:email")
}
```
In the implementation of the `getTasks` query, `Task` is a Wasp Entity that corresponds to the `Task` model defined in the `schema.prisma` file.
The same goes for the `myJob` job and `fooBar` API, where `Task` is used as an Entity.
To learn more about the relationship between Wasp Entities and Prisma models, check out the [Entities](./entities.md) page.
### Wasp-specific Prisma configuration
Wasp mostly lets you use the Prisma schema file as you would in any other JS/TS project. However, there are some Wasp-specific rules you need to follow.
#### The `datasource` block
```prisma title="schema.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
```
Wasp takes the `datasource` you write and use it as-is.
There are some rules you need to follow:
- You can only use `"postgresql"` or `"sqlite"` as the `provider` because Wasp only supports PostgreSQL and SQLite databases for now.
- You must set the `url` field to `env("DATABASE_URL")` so that Wasp can work properly with your database.
#### The `generator` blocks
```prisma title="schema.prisma"
generator client {
provider = "prisma-client-js"
}
```
Wasp requires that there is a `generator` block with `provider = "prisma-client-js"` in the `schema.prisma` file.
You can add additional generators if you need them in your project.
#### The `model` blocks
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
tasks Task[]
}
model Task {
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
}
```
You can define your models in any way you like, if it's valid Prisma schema code, it will work with Wasp.
#### The `enum` blocks
As our applications grow in complexity, we might want to use [Prisma `enum`s](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-enums) to closely define our app's domain. For example, if we had a `Task` model with a boolean `isDone`, and we wanted to start to track whether it is in progress, we could migrate the field to a more expressive type:
```prisma title="schema.prisma"
enum TaskStatus {
NotStarted
Doing
Done
}
model Task {
...
state TaskStatus @default(NotStarted)
}
```
Make sure to check [Prisma's enum compatibility with your database](https://www.prisma.io/docs/orm/reference/database-features#misc).
If it works with Prisma, it will work with Wasp.
##### How to use `enum`s in your code
If you need to access your `enum` cases and their values from your server, you can import them directly from `@prisma/client`:
```js title="src/queries.js"
import { TaskState } from "@prisma/client";
import { Task } from "wasp/entities";
export const getOpenTasks = async (args, context) => {
return context.entities.Task.findMany({
orderBy: { id: "asc" },
where: { NOT: { state: TaskState.Done } },
});
};
```
```ts title="src/queries.ts"
import { TaskState } from "@prisma/client";
import { Task } from "wasp/entities";
import { type GetTasks } from "wasp/server/operations";
export const getOpenTasks: GetTasks = async (args, context) => {
return context.entities.Task.findMany({
orderBy: { id: "asc" },
where: { NOT: { state: TaskState.Done } },
});
};
```
You can also access them from your client code:
```js title="src/views/TaskList.jsx"
import { TaskState } from "@prisma/client";
const TaskRow = ({ task }) => {
return (
);
};
```
:::note Triple slash comments
Wasp only supports `///` in the _leading_ position:
```prisma title="schema.prisma"
model User {
// highlight-next-line
/// The unique identifier for the user.
id Int @id @default(autoincrement())
}
```
However, Wasp does not support `///` comments in the _trailing_ position:
```prisma title="schema.prisma"
model User {
// highlight-next-line
id Int @id @default(autoincrement()) /// This is not supported
}
```
We are aware of this issue and are tracking it at [#3041](https://github.com/wasp-lang/wasp/issues/3041), let us know if this is something you need.
:::
### Prisma preview features
Prisma is still in active development and some of its features are not yet stable. To enable various preview features in Prisma, you need to add the `previewFeatures` field to the `generator` block in the `schema.prisma` file.
For example, one useful Prisma preview feature is PostgreSQL extensions support, which allows you to use PostgreSQL extensions like `pg_vector` or `pg_trgm` in your database schema:
```prisma title="schema.prisma"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
extensions = [pgvector(map: "vector")]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
// ...
```
Read more about preview features in the Prisma docs [here](https://www.prisma.io/docs/orm/reference/preview-features/client-preview-features) or about using PostgreSQL extensions [here](https://www.prisma.io/docs/orm/prisma-schema/postgresql-extensions).
------
# Authentication
## Authentication Overview
Auth is an essential piece of any serious application. That's why Wasp provides authentication and authorization support out of the box.
Here's a 1-minute tour of how full-stack auth works in Wasp:
Enabling auth for your app is optional and can be done by configuring the `auth` field of your `app` declaration:
```wasp title="main.wasp"
app MyApp {
title: "My app",
//...
auth: {
userEntity: User,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
google: {},
gitHub: {},
},
onAuthFailedRedirectTo: "/someRoute"
}
}
//...
```
Read more about the `auth` field options in the [API Reference](#api-reference) section.
We will provide a quick overview of auth in Wasp and link to more detailed documentation for each auth method.
### Available auth methods
Wasp supports the following auth methods:
Let's say we enabled the [Username & password](../auth/username-and-pass) authentication.
We get an auth backend with signup and login endpoints. We also get the `user` object in our [Operations](../data-model/operations/overview) and we can decide what to do based on whether the user is logged in or not.
We would also get the [Auth UI](../auth/ui) generated for us. We can set up our login and signup pages where our users can **create their account** and **login**. We can then protect certain pages by setting `authRequired: true` for them. This will make sure that only logged-in users can access them.
We will also have access to the `user` object in our frontend code, so we can show different UI to logged-in and logged-out users. For example, we can show the user's name in the header alongside a **logout button** or a login button if the user is not logged in.
### Different ways to use auth
When you have decided which auth methods you want to support, you can also choose how you want to present the authorization flows to your users.
##### Generated components
This is the fastest way to ship, with Wasp generating ready-made components for your app.
They allow for some customization to make them consistent with your app.
You don't need to implement any UI or logic, and they just work.
##### Make your own UI {#custom-auth-ui}
Wasp is flexible enough to let you completely customize your login and signup interface.
We give you the auth related functions, and you decide how and when to call them.
This allows for total customization of the look-and-feel, and the interaction, but it needs a bit more work.
:::tip
You don't have to choose one _or_ the other! Mix-and-match, and use what you need in each moment.
For example, you can create a custom signup screen, but use Wasp's generated components for login.
:::
##### Custom login and signup actions
The previously discussed options should cover the vast majority of cases. But, for the few instances where it is not enough,
you can [create your own signup flows](./advanced/custom-auth-actions.md), with completely custom logic.
This is not recommended, and reserved for advanced use cases.
Please check first if other Wasp features (mainly [auth hooks](./auth-hooks.md)) can handle your requirements.
### Protecting a page with `authRequired`
When declaring a page, you can set the `authRequired` property.
If you set it to `true`, only authenticated users can access the page. Unauthenticated users are redirected to a route defined by the `app.auth.onAuthFailedRedirectTo` field.
```wasp title="main.wasp"
page MainPage {
component: import Main from "@src/pages/Main",
authRequired: true
}
```
:::caution Requires auth method
You can only use `authRequired` if your app uses one of the [available auth methods](#available-auth-methods).
:::
If `authRequired` is set to `true`, the page's React component (specified by the `component` property) receives the `user` object as a prop. Read more about the `user` object in the [Accessing the logged-in user section](#accessing-the-logged-in-user).
### Logout action
We provide an action for logging out the user. Here's how you can use it:
```tsx title="src/components/LogoutButton.tsx"
const LogoutButton = () => {
return
}
```
### Accessing the logged-in user
You can get access to the `user` object both on the server and on the client. The `user` object contains the logged-in user's data.
The `user` object has all the fields that you defined in your `User` entity. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. For example, if you have a user that signed up using an email and password, the `user` object might look like this:
```ts
const user = {
// User data
id: 'cluqsex9500017cn7i2hwsg17',
address: 'Some address',
// Auth methods specific data
identities: {
email: {
id: 'user@app.com',
isEmailVerified: true,
emailVerificationSentAt: '2024-04-08T10:06:02.204Z',
passwordResetSentAt: null,
},
},
}
```
#### On the client
There are two ways to access the `user` object on the client:
- the `user` prop
- the `useAuth` hook
##### Getting the `user` in authenticated routes
If the page's declaration sets `authRequired` to `true`, the page's React component receives the `user` object as a prop. This is the simplest way to access the user inside an authenticated page:
```wasp title="main.wasp"
// ...
page AccountPage {
component: import Account from "@src/pages/Account",
authRequired: true
}
```
```tsx title="src/pages/Account.tsx" auto-js
const AccountPage = ({ user }: { user: AuthUser }) => {
return (
{JSON.stringify(user, null, 2)}
)
}
export default AccountPage
```
##### Getting the `user` in non-authenticated routes
Wasp provides a React hook you can use in the client components - `useAuth`.
This hook is a thin wrapper over Wasp's `useQuery` hook and returns data in the same format.
```tsx title="src/pages/MainPage.tsx" auto-js
export function Main() {
const { data: user } = useAuth()
if (!user) {
return (
Please login or{' '}
sign up.
)
} else {
return (
<>
>
)
}
}
```
#### On the server
##### Using the `context.user` object
When authentication is enabled, all [queries and actions](../data-model/operations/overview) have access to the `user` object through the `context` argument. `context.user` contains all User entity's fields and the auth identities connected to the user. We strip out the `hashedPassword` field from the identities for security reasons.
```ts title="src/actions.ts" auto-js
type CreateTaskPayload = Pick
export const createTask: CreateTask = async (
args,
context
) => {
if (!context.user) {
throw new HttpError(403)
}
const Task = context.entities.Task
return Task.create({
data: {
description: args.description,
user: {
connect: { id: context.user.id },
},
},
})
}
```
To implement access control in your app, each operation must check `context.user` and decide what to do. For example, if `context.user` is `undefined` inside a private operation, the user's access should be denied.
When using WebSockets, the `user` object is also available on the `socket.data` object. Read more in the [WebSockets section](../advanced/web-sockets#websocketfn-function).
### Sessions
Wasp's auth uses sessions to keep track of the logged-in user. The session is stored in `localStorage` on the client and in the database on the server. Under the hood, Wasp uses the excellent [Lucia Auth v3](https://v3.lucia-auth.com/) library for session management.
When users log in, Wasp creates a session for them and stores it in the database. The session is then sent to the client and stored in `localStorage`. When users log out, Wasp deletes the session from the database and from `localStorage`.
### User Entity
#### Password Hashing
If you are saving a user's password in the database, you should **never** save it as plain text. You can use Wasp's helper functions for serializing and deserializing provider data which will automatically hash the password for you:
```wasp title="main.wasp"
// ...
action updatePassword {
fn: import { updatePassword } from "@src/auth",
}
```
```ts title="src/auth.ts" auto-js
createProviderId,
findAuthIdentity,
updateAuthIdentityProviderData,
getProviderDataWithPassword,
} from 'wasp/server/auth'
export const updatePassword: UpdatePassword<
{ email: string; password: string },
void
> = async (args, context) => {
const providerId = createProviderId('email', args.email)
const authIdentity = await findAuthIdentity(providerId)
if (!authIdentity) {
throw new HttpError(400, 'Unknown user')
}
const providerData = getProviderDataWithPassword<'email'>(
authIdentity.providerData
)
// Updates the password and hashes it automatically.
await updateAuthIdentityProviderData(providerId, providerData, {
hashedPassword: args.password,
})
}
```
#### Default Validations
When you are using the default authentication flow, Wasp validates the fields with some default validations. These validations run if you use Wasp's built-in [Auth UI](./ui.md) or if you use the provided auth actions.
If you decide to create your [custom auth actions](./advanced/custom-auth-actions.md), you'll need to run the validations yourself.
Default validations depend on the auth method you use.
##### Username & Password
If you use [Username & password](./username-and-pass) authentication, the default validations are:
- The `username` must not be empty
- The `password` must not be empty, have at least 8 characters, and contain a number
Note that `username`s are stored in a **case-insensitive** manner.
##### Email
If you use [Email](./email.md) authentication, the default validations are:
- The `email` must not be empty and a valid email address
- The `password` must not be empty, have at least 8 characters, and contain a number
Note that `email`s are stored in a **case-insensitive** manner.
### Customizing the Signup Process
Sometimes you want to include **extra fields** in your signup process, like first name and last name and save them in the `User` entity.
For this to happen:
- you need to define the fields that you want saved in the database,
- you need to customize the `SignupForm` (in the case of [Email](./email.md) or [Username & Password](./username-and-pass.md) auth)
Other times, you might need to just add some **extra UI** elements to the form, like a checkbox for terms of service. In this case, customizing only the UI components is enough.
Let's see how to do both.
#### 1. Defining Extra Fields
If we want to **save** some extra fields in our signup process, we need to tell our app they exist.
We do that by defining an object where the keys represent the field name, and the values are functions that receive the data sent from the client\* and return the value of the field.
\* We exclude the `password` field from this object to prevent it from being saved as plain-text in the database. The `password` field is handled by Wasp's auth backend.
First, we add the `auth.methods.{authMethod}.userSignupFields` field in our `main.wasp` file. The `{authMethod}` depends on the auth method you are using.
For example, if you are using [Username & Password](./username-and-pass), you would add the `auth.methods.usernameAndPassword.userSignupFields` field:
```wasp title="main.wasp"
app crudTesting {
// ...
auth: {
userEntity: User,
methods: {
usernameAndPassword: {
userSignupFields: import { userSignupFields } from "@src/auth/signup",
},
},
onAuthFailedRedirectTo: "/login",
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
address String?
}
```
Then we'll define the `userSignupFields` object in the `src/auth/signup.{js,ts}` file:
```ts title="src/auth/signup.ts" auto-js
export const userSignupFields = defineUserSignupFields({
address: async (data) => {
const address = data.address
if (typeof address !== 'string') {
throw new Error('Address is required')
}
if (address.length < 5) {
throw new Error('Address must be at least 5 characters long')
}
return address
},
})
```
Read more about the `userSignupFields` object in the [API Reference](#signup-fields-customization).
Keep in mind, that these field names need to exist on the `userEntity` you defined in your `main.wasp` file e.g. `address` needs to be a field on the `User` entity you defined in the `schema.prisma` file.
The field function will receive the data sent from the client and it needs to return the value that will be saved into the database. If the field is invalid, the function should throw an error.
:::info Using Validation Libraries
You can use any validation library you want to validate the fields. For example, you can use `zod` like this:
Click to see the code
```ts title="src/auth/signup.ts" auto-js
import { defineUserSignupFields } from 'wasp/server/auth'
import * as z from 'zod'
export const userSignupFields = defineUserSignupFields({
address: (data) => {
const AddressSchema = z
.string({
required_error: 'Address is required',
invalid_type_error: 'Address must be a string',
})
.min(10, 'Address must be at least 10 characters long')
const result = AddressSchema.safeParse(data.address)
if (result.success === false) {
throw new Error(result.error.issues[0].message)
}
return result.data
},
})
```
:::
Now that we defined the fields, Wasp knows how to:
1. Validate the data sent from the client
2. Save the data to the database
Next, let's see how to customize [Auth UI](../auth/ui) to include those fields.
#### 2. Customizing the Signup Component
:::tip Using Custom Signup Component
If you are not using Wasp's Auth UI, you can skip this section. Just make sure to include the extra fields in your custom signup form.
Read more about using the signup actions for:
- [Email auth](./email/create-your-own-ui.md)
- [Username & password auth](./username-and-pass/create-your-own-ui.md)
:::
If you are using Wasp's Auth UI, you can customize the `SignupForm` component by passing the `additionalFields` prop to it. It can be either a list of extra fields or a render function.
##### Using a List of Extra Fields
When you pass in a list of extra fields to the `SignupForm`, they are added to the form one by one, in the order you pass them in.
Inside the list, there can be either **objects** or **render functions** (you can combine them):
1. Objects are a simple way to describe new fields you need, but a bit less flexible than render functions.
2. Render functions can be used to render any UI you want, but they require a bit more code. The render functions receive the `react-hook-form` object and the form state object as arguments.
```tsx title="src/SignupPage.tsx" auto-js
SignupForm,
FormError,
FormInput,
FormItemGroup,
FormLabel,
} from 'wasp/client/auth'
export const SignupPage = () => {
return (
{
return (
Phone Number
{form.formState.errors.phoneNumber && (
{form.formState.errors.phoneNumber.message}
)}
)
},
]}
/>
)
}
```
Read more about the extra fields in the [API Reference](#signupform-customization).
##### Using a Single Render Function
Instead of passing in a list of extra fields, you can pass in a render function which will receive the `react-hook-form` object and the form state object as arguments. What ever the render function returns, will be rendered below the default fields.
```tsx title="src/SignupPage.tsx" auto-js
export const SignupPage = () => {
return (
{
const username = form.watch('username')
return (
username && (
Hello there {username} đ
)
)
}}
/>
)
}
```
Read more about the render function in the [API Reference](#signupform-customization).
### API Reference
#### Auth Fields
```wasp title="main.wasp"
app MyApp {
title: "My app",
//...
auth: {
userEntity: User,
methods: {
usernameAndPassword: {}, // use this or email, not both
email: {}, // use this or usernameAndPassword, not both
google: {},
gitHub: {},
},
onAuthFailedRedirectTo: "/someRoute",
}
}
//...
```
`app.auth` is a dictionary with the following fields:
##### `userEntity: entity` Required!
The entity representing the user connected to your business logic.
##### `methods: dict` Required!
A dictionary of auth methods enabled for the app.
##### `onAuthFailedRedirectTo: String` Required!
The route to which Wasp should redirect unauthenticated user when they try to access a private page (i.e., a page that has `authRequired: true`).
Check out these [essential docs on auth](../tutorial/auth#adding-auth-to-the-project) to see an example of usage.
##### `onAuthSucceededRedirectTo: String`
The route to which Wasp will send a successfully authenticated after a successful login/signup.
The default value is `"/"`.
:::note
Automatic redirect on successful login only works when using the Wasp-provided [Auth UI](../auth/ui).
:::
#### Signup Fields Customization
If you want to add extra fields to the signup process, the server needs to know how to save them to the database. You do that by defining the `auth.methods.{authMethod}.userSignupFields` field in your `main.wasp` file.
```wasp title="main.wasp"
app crudTesting {
// ...
auth: {
userEntity: User,
methods: {
usernameAndPassword: {
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/signup",
},
},
onAuthFailedRedirectTo: "/login",
},
}
```
Then we'll export the `userSignupFields` object from the `src/auth/signup.{js,ts}` file:
```ts title="src/auth/signup.ts" auto-js
export const userSignupFields = defineUserSignupFields({
address: async (data) => {
const address = data.address
if (typeof address !== 'string') {
throw new Error('Address is required')
}
if (address.length < 5) {
throw new Error('Address must be at least 5 characters long')
}
return address
},
})
```
The `userSignupFields` object is an object where the keys represent the field name, and the values are functions that receive the data sent from the client\* and return the value of the field.
If the value that the function received is invalid, the function should throw an error.
\* We exclude the `password` field from this object to prevent it from being saved as plain text in the database. The `password` field is handled by Wasp's auth backend.
#### `SignupForm` Customization
To customize the `SignupForm` component, you need to pass in the `additionalFields` prop. It can be either a list of extra fields or a render function.
```tsx title="src/SignupPage.tsx" auto-js
SignupForm,
FormError,
FormInput,
FormItemGroup,
FormLabel,
} from 'wasp/client/auth'
export const SignupPage = () => {
return (
{
return (
Phone Number
{form.formState.errors.phoneNumber && (
{form.formState.errors.phoneNumber.message}
)}
)
},
]}
/>
)
}
```
The extra fields can be either **objects** or **render functions** (you can combine them):
1. Objects are a simple way to describe new fields you need, but a bit less flexible than render functions.
The objects have the following properties:
- `name` Required!
- the name of the field
- `label` Required!
- the label of the field (used in the UI)
- `type` Required!
- the type of the field, which can be `input` or `textarea`
- `validations`
- an object with the validation rules for the field. The keys are the validation names, and the values are the validation error messages. Read more about the available validation rules in the [react-hook-form docs](https://react-hook-form.com/api/useform/register#register).
2. Render functions receive the `react-hook-form` object and the form state as arguments, and they can use them to render arbitrary UI elements.
The render function has the following signature:
```ts
type AdditionalSignupFieldRenderFn = (
hookForm: UseFormReturn,
formState: FormState
) => React.ReactNode
```
- `form` Required!
The `react-hook-form` object, read more about it in the [react-hook-form docs](https://react-hook-form.com/api/useform). You need to use the `form.register` function to register your fields
- `state` Required!
The form state object, which has the following properties:
- `isLoading: boolean`
Whether the form is currently submitting
## Auth UI
To make using authentication in your app as easy as possible, Wasp generates the server-side code but also the client-side UI for you. It enables you to quickly get the login, signup, password reset and email verification flows in your app.
Below we cover all of the available UI components and how to use them.

:::note
Remember that if you need a more custom approach, you can always [create your own UI](./overview.md#custom-auth-ui).
:::
### Overview
After Wasp generates the UI components for your auth, you can use it as is, or customize it to your liking.
Based on the authentication providers you enabled in your `main.wasp` file, the Auth UI will show the corresponding UI (form and buttons). For example, if you enabled e-mail authentication:
```wasp {5} title="main.wasp"
app MyApp {
//...
auth: {
methods: {
email: {},
},
// ...
}
}
```
```wasp {5} title="main.wasp"
app MyApp {
//...
auth: {
methods: {
email: {},
},
// ...
}
}
```
You'll get the following UI:

And then if you enable Google and Github:
```wasp title="main.wasp" {6-7}
app MyApp {
//...
auth: {
methods: {
email: {},
google: {},
github: {},
},
// ...
}
}
```
```wasp title="main.wasp" {6-7}
app MyApp {
//...
auth: {
methods: {
email: {},
google: {},
github: {},
},
// ...
}
}
```
The form will automatically update to look like this:

Let's go through all of the available components and how to use them.
### Auth Components
The following components are available for you to use in your app:
- [Login form](#login-form)
- [Signup form](#signup-form)
- [Forgot password form](#forgot-password-form)
- [Reset password form](#reset-password-form)
- [Verify email form](#verify-email-form)
#### Login Form
Used with , , , , , and authentication.

You can use the `LoginForm` component to build your login page:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@src/LoginPage"
}
```
```tsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
// Use it like this
export function LoginPage() {
return
}
```
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { LoginPage } from "@src/LoginPage"
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
// Use it like this
export function LoginPage() {
return
}
```
It will automatically show the correct authentication providers based on your `main.wasp` file.
#### Signup Form
Used with , , , , , and authentication.

You can use the `SignupForm` component to build your signup page:
```wasp title="main.wasp"
// ...
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@src/SignupPage"
}
```
```tsx title="src/SignupPage.jsx"
import { SignupForm } from 'wasp/client/auth'
// Use it like this
export function SignupPage() {
return
}
```
```wasp title="main.wasp"
// ...
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { SignupPage } from "@src/SignupPage"
}
```
```tsx title="src/SignupPage.tsx"
import { SignupForm } from 'wasp/client/auth'
// Use it like this
export function SignupPage() {
return
}
```
It will automatically show the correct authentication providers based on your `main.wasp` file.
Read more about customizing the signup process like adding additional fields or extra UI in the [Auth Overview](../auth/overview#customizing-the-signup-process) section.
#### Forgot Password Form
Used with authentication.
If users forget their password, they can use this form to reset it.

You can use the `ForgotPasswordForm` component to build your own forgot password page:
```wasp title="main.wasp"
// ...
route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
page RequestPasswordResetPage {
component: import { ForgotPasswordPage } from "@src/ForgotPasswordPage"
}
```
```tsx title="src/ForgotPasswordPage.jsx"
import { ForgotPasswordForm } from 'wasp/client/auth'
// Use it like this
export function ForgotPasswordPage() {
return
}
```
```wasp title="main.wasp"
// ...
route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
page RequestPasswordResetPage {
component: import { ForgotPasswordPage } from "@src/ForgotPasswordPage"
}
```
```tsx title="src/ForgotPasswordPage.tsx"
import { ForgotPasswordForm } from 'wasp/client/auth'
// Use it like this
export function ForgotPasswordPage() {
return
}
```
#### Reset Password Form
Used with authentication.
After users click on the link in the email they receive after submitting the forgot password form, they will be redirected to this form where they can reset their password.

You can use the `ResetPasswordForm` component to build your reset password page:
```wasp title="main.wasp"
// ...
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
page PasswordResetPage {
component: import { ResetPasswordPage } from "@src/ResetPasswordPage"
}
```
```tsx title="src/ResetPasswordPage.jsx"
import { ResetPasswordForm } from 'wasp/client/auth'
// Use it like this
export function ResetPasswordPage() {
return
}
```
```wasp title="main.wasp"
// ...
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
page PasswordResetPage {
component: import { ResetPasswordPage } from "@src/ResetPasswordPage"
}
```
```tsx title="src/ResetPasswordPage.tsx"
import { ResetPasswordForm } from 'wasp/client/auth'
// Use it like this
export function ResetPasswordPage() {
return
}
```
#### Verify Email Form
Used with authentication.
After users sign up, they will receive an email with a link to this form where they can verify their email.

You can use the `VerifyEmailForm` component to build your email verification page:
```wasp title="main.wasp"
// ...
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
page EmailVerificationPage {
component: import { VerifyEmailPage } from "@src/VerifyEmailPage"
}
```
```tsx title="src/VerifyEmailPage.jsx"
import { VerifyEmailForm } from 'wasp/client/auth'
// Use it like this
export function VerifyEmailPage() {
return
}
```
```wasp title="main.wasp"
// ...
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
page EmailVerificationPage {
component: import { VerifyEmailPage } from "@src/VerifyEmailPage"
}
```
```tsx title="src/VerifyEmailPage.tsx"
import { VerifyEmailForm } from 'wasp/client/auth'
// Use it like this
export function VerifyEmailPage() {
return
}
```
### Customization đ đģ
You customize all of the available forms by passing props to them.
Props you can pass to all of the forms:
1. `appearance` - customize the form colors (via design tokens)
2. `logo` - path to your logo
3. `socialLayout` - layout of the social buttons, which can be `vertical` or `horizontal`
#### 1. Customizing the Colors
We use CSS variables in our styling so you can customize the styles by overriding the default theme tokens.
:::info List of all available tokens
See the [list of all available tokens](https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/sdk/wasp/auth/forms/types.ts) which you can override.
:::
```js title="src/appearance.js"
export const authAppearance = {
colors: {
brand: '#5969b8', // blue
brandAccent: '#de5998', // pink
submitButtonText: 'white',
},
}
```
```jsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
import { authAppearance } from './appearance'
export function LoginPage() {
return (
)
}
```
```ts title="src/appearance.ts"
import type { CustomizationOptions } from 'wasp/client/auth'
export const authAppearance: CustomizationOptions['appearance'] = {
colors: {
brand: '#5969b8', // blue
brandAccent: '#de5998', // pink
submitButtonText: 'white',
},
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
import { authAppearance } from './appearance'
export function LoginPage() {
return (
)
}
```
We recommend defining your appearance in a separate file and importing it into your components.
#### 2. Using Your Logo
You can add your logo to the Auth UI by passing the `logo` prop to any of the components.
```tsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
import Logo from './logo.png'
export function LoginPage() {
return (
)
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
import Logo from './logo.png'
export function LoginPage() {
return (
)
}
```
#### 3. Social Buttons Layout
You can change the layout of the social buttons by passing the `socialLayout` prop to any of the components. It can be either `vertical` or `horizontal` (default).
If we pass in `vertical`:
```tsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
export function LoginPage() {
return (
)
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
export function LoginPage() {
return (
)
}
```
We get this:

#### Let's Put Everything Together đĒ
If we provide the logo and custom colors:
```ts title="src/appearance.js"
export const appearance = {
colors: {
brand: '#5969b8', // blue
brandAccent: '#de5998', // pink
submitButtonText: 'white',
},
}
```
```tsx title="src/LoginPage.jsx"
import { LoginForm } from 'wasp/client/auth'
import { authAppearance } from './appearance'
import todoLogo from './todoLogo.png'
export function LoginPage() {
return
}
```
```ts title="src/appearance.ts"
import type { CustomizationOptions } from 'wasp/client/auth'
export const appearance: CustomizationOptions['appearance'] = {
colors: {
brand: '#5969b8', // blue
brandAccent: '#de5998', // pink
submitButtonText: 'white',
},
}
```
```tsx title="src/LoginPage.tsx"
import { LoginForm } from 'wasp/client/auth'
import { authAppearance } from './appearance'
import todoLogo from './todoLogo.png'
export function LoginPage() {
return
}
```
We get a form looking like this:
## Username & Password Auth Overview
Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client side.
### Setting Up Username & Password Authentication
To set up username authentication we need to:
1. Enable username authentication in the Wasp file
2. Add the `User` entity
3. Add the auth routes and pages
4. Use Auth UI components in our pages
Structure of the `main.wasp` file we will end up with:
```wasp title="main.wasp"
// Configuring e-mail authentication
app myApp {
auth: { ... }
}
// Defining routes and pages
route SignupRoute { ... }
page SignupPage { ... }
// ...
```
#### 1. Enable Username Authentication
Let's start with adding the following to our `main.wasp` file:
```wasp title="main.wasp" {11}
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable username authentication
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
```
```wasp title="main.wasp" {11}
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable username authentication
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
```
Read more about the `usernameAndPassword` auth method options [here](#fields-in-the-usernameandpassword-dict).
#### 2. Add the User Entity
The `User` entity can be as simple as including only the `id` field:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Add the Routes and Pages
Next, we need to define the routes and pages for the authentication pages.
Add the following to the `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth"
}
```
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 4. Create the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../project/css-frameworks).
:::
Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it:
```tsx title="src/pages/auth.jsx"
import { LoginForm, SignupForm } from 'wasp/client/auth'
import { Link } from 'react-router'
export function Login() {
return (
Don't have an account yet? go to signup.
)
}
export function Signup() {
return (
I already have an account (go to login).
)
}
// A layout component to center the content
export function Layout({ children }) {
return (
{children}
)
}
```
```tsx title="src/pages/auth.tsx"
import { LoginForm, SignupForm } from 'wasp/client/auth'
import { Link } from 'react-router'
export function Login() {
return (
Don't have an account yet? go to signup.
)
}
export function Signup() {
return (
I already have an account (go to login).
)
}
// A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../auth/ui).
#### Conclusion
That's it! We have set up username authentication in our app. đ
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with username authentication. If you want to put some of the pages behind authentication, read the [auth overview docs](../auth/overview).
### Using Auth
To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](../auth/overview).
When you receive the `user` object [on the client or the server](./overview.md#accessing-the-logged-in-user), you'll be able to access the user's username like this:
### API Reference
#### `userEntity` fields
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
}
```
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
},
onAuthFailedRedirectTo: "/login"
}
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
}
```
#### Fields in the `usernameAndPassword` dict
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {
userSignupFields: import { userSignupFields } from "@src/auth/email",
},
},
onAuthFailedRedirectTo: "/login"
}
}
// ...
```
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
usernameAndPassword: {
userSignupFields: import { userSignupFields } from "@src/auth/email",
},
},
onAuthFailedRedirectTo: "/login"
}
}
// ...
```
##### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](./overview#1-defining-extra-fields).
## Create your own UI for Username & Password Auth
The login and signup flows are pretty standard: they allow the user to sign up and then log in with their username and password. The signup flow validates the username and password and then creates a new user entity in the database.
:::tip
Read more about the default email and password validation rules in the [auth overview docs](../overview.md#default-validations).
:::
Even though Wasp offers premade [Auth UI](../ui.md) for your authentication flows, there are times where you might want more customization, so we also give you the option to create your own UI and call Wasp's auth actions on your own code, similar to how Auth UI does it under the hood.
### Example code
Below you can find a starting point for making your own UI in the client code. You can customize any of its look and behaviour, just make sure to call the `signup()` or `login()` functions imported from `wasp/client/auth`.
#### Sign-up
```jsx title="src/pages/auth.jsx"
import { login, signup } from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
export function Signup() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await signup({ username, password })
await login({ username, password })
navigate('/')
} catch (error) {
setError(error)
}
}
return (
)
}
```
```tsx title="src/pages/auth.tsx"
import { login, signup } from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
export function Signup() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
await signup({ username, password })
await login({ username, password })
navigate('/')
} catch (error: unknown) {
setError(error as Error)
}
}
return (
)
}
```
#### Login
```jsx title="src/pages/auth.jsx"
import { login } from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
export function Login() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await login({ username, password })
navigate('/')
} catch (error) {
setError(error)
}
}
return (
)
}
```
```tsx title="src/pages/auth.tsx"
import { login } from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
export function Login() {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
await login({ username, password })
navigate('/')
} catch (error: unknown) {
setError(error as Error)
}
}
return (
)
}
```
### API Reference
You can import the following functions from `wasp/client/auth`:
#### `login()`
An action for logging in the user.
It takes one argument:
- `data: object` Required!
It has the following fields:
- `username: string` Required!
- `password: string` Required!
:::note
When using the exposed `login()` function, make sure to implement your redirect on success login logic (e.g. redirecting to home).
:::
#### `signup()`
An action for signing up the user. This action does not log in the user, you still need to call `login()`.
It takes one argument:
- `data: object` Required!
It has the following fields:
- `username: string` Required!
- `password: string` Required!
:::info
By default, Wasp will only save the `username` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](../overview.md#customizing-the-signup-process).
:::
## Email Auth Overview
Wasp supports e-mail authentication out of the box, along with email verification and "forgot your password?" flows. It provides you with the server-side implementation and email templates for all of these flows.

### Setting Up Email Authentication
We'll need to take the following steps to set up email authentication:
1. Enable email authentication in the Wasp file
2. Add the `User` entity
3. Add the auth routes and pages
4. Use Auth UI components in our pages
5. Set up the email sender
Structure of the `main.wasp` file we will end up with:
```wasp title="main.wasp"
// Configuring e-mail authentication
app myApp {
auth: { ... },
emailSender: { ... }
}
// Defining routes and pages
route SignupRoute { ... }
page SignupPage { ... }
// ...
```
#### 1. Enable Email Authentication in `main.wasp`
Let's start with adding the following to our `main.wasp` file:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable email authentication
email: {
// 3. Specify the email from field
fromField: {
name: "My App Postman",
email: "hello@itsme.com"
},
// 4. Specify the email verification and password reset options (we'll talk about them later)
emailVerification: {
clientRoute: EmailVerificationRoute,
},
passwordReset: {
clientRoute: PasswordResetRoute,
},
},
},
onAuthFailedRedirectTo: "/login",
onAuthSucceededRedirectTo: "/"
},
}
```
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the user entity (we'll define it next)
userEntity: User,
methods: {
// 2. Enable email authentication
email: {
// 3. Specify the email from field
fromField: {
name: "My App Postman",
email: "hello@itsme.com"
},
// 4. Specify the email verification and password reset options (we'll talk about them later)
emailVerification: {
clientRoute: EmailVerificationRoute,
},
passwordReset: {
clientRoute: PasswordResetRoute,
},
},
},
onAuthFailedRedirectTo: "/login",
onAuthSucceededRedirectTo: "/"
},
}
```
Read more about the `email` auth method options [here](#fields-in-the-email-dict).
#### 2. Add the User Entity
The `User` entity can be as simple as including only the `id` field:
```prisma title="schema.prisma"
// 5. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
```prisma title="schema.prisma"
// 5. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Add the Routes and Pages
Next, we need to define the routes and pages for the authentication pages.
Add the following to the `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth"
}
route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
page RequestPasswordResetPage {
component: import { RequestPasswordReset } from "@src/pages/auth",
}
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
page PasswordResetPage {
component: import { PasswordReset } from "@src/pages/auth",
}
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
page EmailVerificationPage {
component: import { EmailVerification } from "@src/pages/auth",
}
```
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
route SignupRoute { path: "/signup", to: SignupPage }
page SignupPage {
component: import { Signup } from "@src/pages/auth"
}
route RequestPasswordResetRoute { path: "/request-password-reset", to: RequestPasswordResetPage }
page RequestPasswordResetPage {
component: import { RequestPasswordReset } from "@src/pages/auth",
}
route PasswordResetRoute { path: "/password-reset", to: PasswordResetPage }
page PasswordResetPage {
component: import { PasswordReset } from "@src/pages/auth",
}
route EmailVerificationRoute { path: "/email-verification", to: EmailVerificationPage }
page EmailVerificationPage {
component: import { EmailVerification } from "@src/pages/auth",
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 4. Create the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../project/css-frameworks).
:::
Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it:
```tsx title="src/pages/auth.jsx"
import {
LoginForm,
SignupForm,
VerifyEmailForm,
ForgotPasswordForm,
ResetPasswordForm,
} from 'wasp/client/auth'
import { Link } from 'react-router'
export function Login() {
return (
Don't have an account yet? go to signup.
Forgot your password? reset it.
)
}
export function Signup() {
return (
I already have an account (go to login).
)
}
export function EmailVerification() {
return (
If everything is okay, go to login
)
}
export function RequestPasswordReset() {
return (
)
}
export function PasswordReset() {
return (
If everything is okay, go to login
)
}
// A layout component to center the content
export function Layout({ children }) {
return (
{children}
)
}
```
```tsx title="src/pages/auth.tsx"
import {
LoginForm,
SignupForm,
VerifyEmailForm,
ForgotPasswordForm,
ResetPasswordForm,
} from 'wasp/client/auth'
import { Link } from 'react-router'
export function Login() {
return (
Don't have an account yet? go to signup.
Forgot your password? reset it.
)
}
export function Signup() {
return (
I already have an account (go to login).
)
}
export function EmailVerification() {
return (
If everything is okay, go to login
)
}
export function RequestPasswordReset() {
return (
)
}
export function PasswordReset() {
return (
If everything is okay, go to login
)
}
// A layout component to center the content
export function Layout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../auth/ui).
#### 5. Set up an Email Sender
To support e-mail verification and password reset flows, we need an e-mail sender. Luckily, Wasp supports several email providers out of the box.
We'll use the `Dummy` provider to speed up the setup. It just logs the emails to the console instead of sending them. You can use any of the [supported email providers](../advanced/email#providers).
To set up the `Dummy` provider to send emails, add the following to the `main.wasp` file:
```wasp title="main.wasp"
app myApp {
// ...
// 7. Set up the email sender
emailSender: {
provider: Dummy,
}
}
```
```wasp title="main.wasp"
app myApp {
// ...
// 7. Set up the email sender
emailSender: {
provider: Dummy,
}
}
```
#### Conclusion
That's it! We have set up email authentication in our app. đ
Running `wasp db migrate-dev` and then `wasp start` should give you a working app with email authentication. If you want to put some of the pages behind authentication, read the [auth overview](../auth/overview).
### Login and Signup Flows
#### Login

#### Signup

Some of the behavior you get out of the box:
1. Rate limiting
We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming.
2. Preventing user email leaks
If somebody tries to signup with an email that already exists and it's verified, we _pretend_ that the account was created instead of saying it's an existing account. This is done to prevent leaking the user's email address.
3. Allowing registration for unverified emails
If a user tries to register with an existing but **unverified** email, we'll allow them to do that. This is done to prevent bad actors from locking out other users from registering with their email address.
4. Password validation
Read more about the default password validation rules and how to override them in [auth overview docs](../auth/overview).
### Email Verification Flow
:::info Automatic email verification in development
In development mode, you can skip the email verification step by setting the `SKIP_EMAIL_VERIFICATION_IN_DEV` environment variable to `true` in your `.env.server` file:
```env title=".env.server"
SKIP_EMAIL_VERIFICATION_IN_DEV=true
```
This is useful when you are developing your app and don't want to go through the email verification flow every time you sign up. It can be also useful when you are writing automated tests for your app.
:::
By default, Wasp requires the e-mail to be verified before allowing the user to log in. This is done by sending a verification email to the user's email address and requiring the user to click on a link in the email to verify their email address.
Our setup looks like this:
```wasp title="main.wasp"
// ...
emailVerification: {
clientRoute: EmailVerificationRoute,
}
```
```wasp title="main.wasp"
// ...
emailVerification: {
clientRoute: EmailVerificationRoute,
}
```
When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `EmailVerificationRoute` route we defined in the `main.wasp` file.
The content of the e-mail can be customized, read more about it [here](#emailverification-emailverificationconfig-).
#### Email Verification Page
We defined our email verification page in the `auth.{jsx,tsx}` file.

### Password Reset Flow
Users can request a password and then they'll receive an e-mail with a link to reset their password.
Some of the behavior you get out of the box:
1. Rate limiting
We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming.
2. Preventing user email leaks
If somebody requests a password reset with an unknown email address, we'll give back the same response as if the user requested a password reset successfully. This is done to prevent leaking information.
Our setup in `main.wasp` looks like this:
```wasp title="main.wasp"
// ...
passwordReset: {
clientRoute: PasswordResetRoute,
}
```
```wasp title="main.wasp"
// ...
passwordReset: {
clientRoute: PasswordResetRoute,
}
```
#### Request Password Reset Page
Users request their password to be reset by going to the `/request-password-reset` route. We defined our request password reset page in the `auth.{jsx,tsx}` file.

#### Password Reset Page
When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `PasswordResetRoute` route we defined in the `main.wasp` file.

Users can enter their new password there.
The content of the e-mail can be customized, read more about it [here](#passwordreset-passwordresetconfig-).
##### Password
- `ensurePasswordIsPresent(args)`
Checks if the password is present and throws an error if it's not.
- `ensureValidPassword(args)`
Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](../auth/overview#default-validations).
### Using Auth
To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](../auth/overview).
When you receive the `user` object [on the client or the server](./overview.md#accessing-the-logged-in-user), you'll be able to access the user's email and other information like this:
### API Reference
Let's go over the options we can specify when using email authentication.
#### `userEntity` fields
```wasp title="main.wasp"
app myApp {
title: "My app",
// ...
auth: {
userEntity: User,
methods: {
email: {
// We'll explain these options below
},
},
onAuthFailedRedirectTo: "/someRoute"
},
// ...
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
}
```
```wasp title="main.wasp"
app myApp {
title: "My app",
// ...
auth: {
userEntity: User,
methods: {
email: {
// We'll explain these options below
},
},
onAuthFailedRedirectTo: "/someRoute"
},
// ...
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
}
```
#### Fields in the `email` dict
```wasp title="main.wasp"
app myApp {
title: "My app",
// ...
auth: {
userEntity: User,
methods: {
email: {
userSignupFields: import { userSignupFields } from "@src/auth",
fromField: {
name: "My App",
email: "hello@itsme.com"
},
emailVerification: {
clientRoute: EmailVerificationRoute,
getEmailContentFn: import { getVerificationEmailContent } from "@src/auth/email",
},
passwordReset: {
clientRoute: PasswordResetRoute,
getEmailContentFn: import { getPasswordResetEmailContent } from "@src/auth/email",
},
},
},
onAuthFailedRedirectTo: "/someRoute"
},
// ...
}
```
```wasp title="main.wasp"
app myApp {
title: "My app",
// ...
auth: {
userEntity: User,
methods: {
email: {
userSignupFields: import { userSignupFields } from "@src/auth",
fromField: {
name: "My App",
email: "hello@itsme.com"
},
emailVerification: {
clientRoute: EmailVerificationRoute,
getEmailContentFn: import { getVerificationEmailContent } from "@src/auth/email",
},
passwordReset: {
clientRoute: PasswordResetRoute,
getEmailContentFn: import { getPasswordResetEmailContent } from "@src/auth/email",
},
},
},
onAuthFailedRedirectTo: "/someRoute"
},
// ...
}
```
##### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](./overview#1-defining-extra-fields).
##### `fromField: EmailFromField` Required!
`fromField` is a dict that specifies the name and e-mail address of the sender of the e-mails sent by your app.
It has the following fields:
- `name`: name of the sender
- `email`: e-mail address of the sender Required!
##### `emailVerification: EmailVerificationConfig` Required!
`emailVerification` is a dict that specifies the details of the e-mail verification process.
It has the following fields:
- `clientRoute: Route`: a route that is used for the user to verify their e-mail address. Required!
Client route should handle the process of taking a token from the URL and sending it to the server to verify the e-mail address. You can use our `verifyEmail` action for that.
```js title="src/pages/EmailVerificationPage.jsx"
import { verifyEmail } from 'wasp/client/auth'
...
await verifyEmail({ token });
```
```ts title="src/pages/EmailVerificationPage.tsx"
import { verifyEmail } from 'wasp/client/auth'
...
await verifyEmail({ token });
```
:::note
We used Auth UI above to avoid doing this work of sending the token to the server manually.
:::
- `getEmailContentFn: ExtImport`: a function that returns the content of the e-mail that is sent to the user.
Defining `getEmailContentFn` can be done by defining a file in the `src` directory.
```ts title="src/email.js"
export const getVerificationEmailContent = ({ verificationLink }) => ({
subject: 'Verify your email',
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
Click the link below to verify your email
Verify email
`,
})
```
```ts title="src/email.ts"
import { GetVerificationEmailContentFn } from 'wasp/server/auth'
export const getVerificationEmailContent: GetVerificationEmailContentFn = ({
verificationLink,
}) => ({
subject: 'Verify your email',
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
Click the link below to verify your email
Verify email
`,
})
```
This is the default content of the e-mail, you can customize it to your liking.
##### `passwordReset: PasswordResetConfig` Required!
`passwordReset` is a dict that specifies the password reset process.
It has the following fields:
- `clientRoute: Route`: a route that is used for the user to reset their password. Required!
Client route should handle the process of taking a token from the URL and a new password from the user and sending it to the server. You can use our `requestPasswordReset` and `resetPassword` actions to do that.
```js title="src/pages/ForgotPasswordPage.jsx"
import { requestPasswordReset } from 'wasp/client/auth'
...
await requestPasswordReset({ email });
```
```js title="src/pages/PasswordResetPage.jsx"
import { resetPassword } from 'wasp/client/auth'
...
await resetPassword({ password, token })
```
```ts title="src/pages/ForgotPasswordPage.tsx"
import { requestPasswordReset } from 'wasp/client/auth'
...
await requestPasswordReset({ email });
```
```ts title="src/pages/PasswordResetPage.tsx"
import { resetPassword } from 'wasp/client/auth'
...
await resetPassword({ password, token })
```
:::note
We used Auth UI above to avoid doing this work of sending the password request and the new password to the server manually.
:::
- `getEmailContentFn: ExtImport`: a function that returns the content of the e-mail that is sent to the user.
Defining `getEmailContentFn` is done by defining a function that looks like this:
```ts title="src/email.js"
export const getPasswordResetEmailContent = ({ passwordResetLink }) => ({
subject: 'Password reset',
text: `Click the link below to reset your password: ${passwordResetLink}`,
html: `
Click the link below to reset your password
Reset password
`,
})
```
```ts title="src/email.ts"
import { GetPasswordResetEmailContentFn } from 'wasp/server/auth'
export const getPasswordResetEmailContent: GetPasswordResetEmailContentFn = ({
passwordResetLink,
}) => ({
subject: 'Password reset',
text: `Click the link below to reset your password: ${passwordResetLink}`,
html: `
Click the link below to reset your password
Reset password
`,
})
```
This is the default content of the e-mail, you can customize it to your liking.
## Create your own UI for Email Auth
When using the email auth provider, users log in with their email address and a password. On signup, Wasp validates the data and sends a verification email. The user account is not active until the user clicks the link in the verification email. Also, the user can reset their password through a similar flow.
:::tip
Read more about the default email and password validation rules in the [auth overview docs](../overview.md#default-validations).
:::
Even though Wasp offers premade [Auth UI](../ui.md) for your authentication flows, there are times when you might want more customization, so we also give you the option to create your own UI and call Wasp's auth actions from your own code, similar to how Auth UI does it under the hood.
### Example code
Below you can find a starting point for making your own UI in the client code. This example has all the necessary components to handle login, signup, email verification, and the password reset flow. You can customize any of its look and behaviour, just make sure to call the functions imported from `wasp/client/auth`.
```jsx title="src/pages/auth.jsx"
import {
login,
requestPasswordReset,
resetPassword,
signup,
verifyEmail,
} from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
// This will be shown when the user wants to log in
export function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await login({ email, password })
navigate('/')
} catch (error) {
setError(error)
}
}
return (
)
}
// This will be shown when the user wants to sign up
export function Signup() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await signup({ email, password })
setNeedsConfirmation(true)
} catch (error) {
console.error('Error during signup:', error)
setError(error)
}
}
if (needsConfirmation) {
return (
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
)
}
return (
)
}
// This will be shown has clicked on the link in their
// email to verify their email address
export function EmailVerification() {
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleClick() {
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await verifyEmail({ token })
navigate('/')
} catch (error) {
console.error('Error during email verification:', error)
setError(error)
}
}
return (
<>
{error &&
Error: {error.message}
}
>
)
}
// This will be shown when the user wants to reset their password
export function RequestPasswordReset() {
const [email, setEmail] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
await requestPasswordReset({ email })
setNeedsConfirmation(true)
} catch (error) {
console.error('Error during requesting reset:', error)
setError(error)
}
}
if (needsConfirmation) {
return (
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
)
}
return (
)
}
// This will be shown when the user clicks on the link in their
// email to reset their password
export function PasswordReset() {
const [error, setError] = useState(null)
const [newPassword, setNewPassword] = useState('')
const navigate = useNavigate()
async function handleSubmit(event) {
event.preventDefault()
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await resetPassword({ token, password: newPassword })
navigate('/')
} catch (error) {
console.error('Error during password reset:', error)
setError(error)
}
}
return (
)
}
```
```tsx title="src/pages/auth.tsx"
import {
login,
requestPasswordReset,
resetPassword,
signup,
verifyEmail,
} from 'wasp/client/auth'
import { useState } from 'react'
import { useNavigate } from 'react-router'
// This will be shown when the user wants to log in
export function Login() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
await login({ email, password })
navigate('/')
} catch (error: unknown) {
setError(error as Error)
}
}
return (
)
}
// This will be shown when the user wants to sign up
export function Signup() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
await signup({ email, password })
setNeedsConfirmation(true)
} catch (error: unknown) {
console.error('Error during signup:', error)
setError(error as Error)
}
}
if (needsConfirmation) {
return (
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
)
}
return (
)
}
// This will be shown has clicked on the link in their
// email to verify their email address
export function EmailVerification() {
const [error, setError] = useState(null)
const navigate = useNavigate()
async function handleClick() {
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await verifyEmail({ token })
navigate('/')
} catch (error: unknown) {
console.error('Error during email verification:', error)
setError(error as Error)
}
}
return (
<>
{error &&
Error: {error.message}
}
>
)
}
// This will be shown when the user wants to reset their password
export function RequestPasswordReset() {
const [email, setEmail] = useState('')
const [error, setError] = useState(null)
const [needsConfirmation, setNeedsConfirmation] = useState(false)
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
await requestPasswordReset({ email })
setNeedsConfirmation(true)
} catch (error: unknown) {
console.error('Error during requesting reset:', error)
setError(error as Error)
}
}
if (needsConfirmation) {
return (
Check your email for the confirmation link. If you don't see it, check
spam/junk folder.
)
}
return (
)
}
// This will be shown when the user clicks on the link in their
// email to reset their password
export function PasswordReset() {
const [error, setError] = useState(null)
const [newPassword, setNewPassword] = useState('')
const navigate = useNavigate()
async function handleSubmit(event: React.FormEvent) {
event.preventDefault()
setError(null)
try {
// The token is passed as a query parameter
const token = new URLSearchParams(window.location.search).get('token')
if (!token) throw new Error('Token not found in URL')
await resetPassword({ token, password: newPassword })
navigate('/')
} catch (error: unknown) {
console.error('Error during password reset:', error)
setError(error as Error)
}
}
return (
)
}
```
### API Reference
You can import the following functions from `wasp/client/auth`:
#### `login()`
An action for logging in the user. Make sure to do a redirect on success (e.g. to the main page of the app).
It takes one argument:
- `data: object` Required!
It has the following fields:
- `email: string` Required!
- `password: string` Required!
#### `signup()`
An action for signing up the user and starting the email verification. The user will not be logged in after this, as they still need
to verify their email.
It takes one argument:
- `data: object` Required!
It has the following fields:
- `email: string` Required!
- `password: string` Required!
:::info
By default, Wasp will only save the `email` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](../overview.md#customizing-the-signup-process).
:::
#### `verifyEmail()`
An action for marking the email as valid and the user account as active. Make sure to do a redirect on success (e.g. to the login page).
It takes one argument:
- `data: object` Required!
It has the following fields:
- `token: string` Required!
The token that was created when signing up. It will be set as a URL Query Parameter named `token`.
#### `requestPasswordReset()`
An action for asking for a password reset email. This doesn't immediately reset their password, just sends the email.
It takes one argument:
- `data: object` Required!
It has the following fields:
- `email: string` Required!
#### `resetPassword()`
An action for confirming a password reset and providing the new password. Make sure to do a redirect on success (e.g. to the login page).
It takes one argument:
- `data: object` Required!
It has the following fields:
- `token: string` Required!
The token that was created when requesting the password reset. It will be set as a URL Query Parameter named `token`.
- `password: string` Required!
The new password for the user.
## Social Auth Overview
Social login options (e.g., _Log in with Google_) are a great (maybe even the best) solution for handling user accounts.
A famous old developer joke tells us _"The best auth system is the one you never have to make."_
Wasp wants to make adding social login options to your app as painless as possible.
Using different social providers gives users a chance to sign into your app via their existing accounts on other platforms (Google, GitHub, etc.).
This page goes through the common behaviors between all supported social login providers and shows you how to customize them.
It also gives an overview of Wasp's UI helpers - the quickest possible way to get started with social auth.
### Available Providers
Wasp currently supports the following social login providers:
### User Entity
Wasp requires you to declare a `userEntity` for all `auth` methods (social or otherwise).
This field tells Wasp which Entity represents the user.
Here's what the full setup looks like:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// highlight-next-line
userEntity: User,
methods: {
google: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
// highlight-next-line
model User {
id Int @id @default(autoincrement())
}
```
### Default Behavior
### Overrides
By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider.
If you wish to store more information about the user, you can override the default behavior. You can do this by defining the `userSignupFields` and `configFn` fields in `main.wasp` for each provider.
You can create custom signup setups, such as allowing users to define a custom username after they sign up with a social provider.
#### Example: Allowing User to Set Their Username
If you want to modify the signup flow (e.g., let users choose their own usernames), you will need to go through three steps:
1. The first step is adding a `isSignupComplete` property to your `User` Entity. This field will signal whether the user has completed the signup process.
2. The second step is overriding the default signup behavior.
3. The third step is implementing the rest of your signup flow and redirecting users where appropriate.
Let's go through both steps in more detail.
##### 1. Adding the `isSignupComplete` Field to the `User` Entity
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String? @unique
// highlight-next-line
isSignupComplete Boolean @default(false)
}
```
##### 2. Overriding the Default Behavior
Declare an import under `app.auth.methods.google.userSignupFields` (the example assumes you're using Google):
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
google: {
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/google"
}
},
onAuthFailedRedirectTo: "/login"
},
}
// ...
```
And implement the imported function:
```ts title="src/auth/google.ts" auto-js
export const userSignupFields = defineUserSignupFields({
isSignupComplete: () => false,
})
```
##### 3. Showing the Correct State on the Client
You can check the `isSignupComplete` flag on the `user` object.
Authenticated pages come with the [`user` prop](../../auth/overview#getting-the-user-in-authenticated-routes) which gives you access to the current user. If the `user` prop is out of reach, fetch the current user with the [`useAuth()` hook](../../auth/overview#getting-the-user-in-non-authenticated-routes).
Depending on the flag's value, you can redirect users to the appropriate signup step.
For example:
1. When the user lands on the homepage, check the value of `user.isSignupComplete`.
2. If it's `false`, it means the user has started the signup process but hasn't yet chosen their username. Therefore, you can redirect them to `EditUserDetailsPage` where they can edit the `username` property.
```tsx title="src/HomePage.tsx" auto-js
export function HomePage({ user }: { user: AuthUser }) {
if (user.isSignupComplete === false) {
return
}
// ...
}
```
The same general principle applies to more complex signup procedures, just change the boolean `isSignupComplete` property to a property like `currentSignupStep` that can hold more values.
#### Using the User's Provider Account Details
Account details are provider-specific.
Each provider has their own rules for defining the `userSignupFields` and `configFn` fields:
### API Reference
#### Fields in the `app.auth` Dictionary and Overrides
For more information on:
- Allowed fields in `app.auth`
- `userSignupFields` and `configFn` functions
Check the provider-specific API References:
## GitHub
Wasp supports GitHub Authentication out of the box.
GitHub is a great external auth choice when you're building apps for developers, as most of them already have a GitHub account.
Letting your users log in using their GitHub accounts turns the signup process into a breeze.
Let's walk through enabling GitHub Authentication, explain some of the default settings, and show how to override them.
### Setting up GitHub Auth
Enabling GitHub Authentication comes down to a series of steps:
1. Enabling GitHub authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating a GitHub OAuth app.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Adding GitHub Auth to Your Wasp File
Let's start by properly configuring the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// highlight-next-line
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// highlight-next-line
// 2. Enable GitHub Auth
// highlight-next-line
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
#### 2. Add the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a GitHub OAuth App
To use GitHub as an authentication method, you'll first need to create a GitHub App and provide Wasp with your client key and secret. Here's how you do it:
1. Create a GitHub account if you do not already have one: https://github.com.
2. Create and configure a new GitHub App: https://github.com/settings/apps.

3. We have to fill out **App name** and **Homepage URL** fields.
Additionally we will add our authorization **Callback URL**s:
- For development, put: `http://localhost:3001/auth/github/callback`.
- Once you know your production URL you can add it via the **Add Callback URL** button, e.g. `https://your-server-url.com/auth/github/callback`.

4. We will turn off the **Active** checkbox under the Webhook title, and then we can click **Create GitHub App**.

5. Finally, we can click **Generate a new client secret** button and then copy both **Client ID** and **Client secret**.

#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
```
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Creating the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../../auth/ui).
#### Conclusion
Yay, we've successfully set up GitHub Auth! đ

Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Default Behaviour
Add `gitHub: {}` to the `auth.methods` dictionary to use it with default settings.
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
gitHub: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From GitHub
We are using GitHub's API and its `/user` and `/user/emails` endpoints to get the user data.
:::info We combine the data from the two endpoints
You'll find the emails in the `emails` property in the object that you receive in `userSignupFields`.
This is because we combine the data from the `/user` and `/user/emails` endpoints **if the `user` or `user:email` scope is requested.**
:::
The data we receive from GitHub on the `/user` endpoint looks something this:
```json
{
"login": "octocat",
"id": 1,
"name": "monalisa octocat",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": ""
// ...
}
```
And the data from the `/user/emails` endpoint looks something like this:
```json
[
{
"email": "octocat@github.com",
"verified": true,
"primary": true,
"visibility": "public"
}
]
```
The fields you receive will depend on the scopes you requested. By default we don't specify any scopes. If you want to get the emails, you need to specify the `user` or `user:email` scope in the `configFn` function.
For an up to date info about the data received from GitHub, please refer to the [GitHub API documentation](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user).
#### Using the Data Received From GitHub
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/github",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/github"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String
}
// ...
```
```ts title="src/auth/github.ts" auto-js
export const userSignupFields = defineUserSignupFields({
username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name,
})
export function getConfig() {
return {
scopes: ['user'],
}
}
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's GitHub ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
gitHub: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/github",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/github"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `gitHub` dict has the following properties:
- #### `configFn: ExtImport`
This function should return an object with the scopes for the OAuth provider.
```ts title="src/auth/github.ts" auto-js
export function getConfig() {
return {
scopes: [],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Google
Wasp supports Google Authentication out of the box.
Google Auth is arguably the best external auth option, as most users on the web already have Google accounts.
Enabling it lets your users log in using their existing Google accounts, greatly simplifying the process and enhancing the user experience.
Let's walk through enabling Google authentication, explain some of the default settings, and show how to override them.
### Setting up Google Auth
Enabling Google Authentication comes down to a series of steps:
1. Enabling Google authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating a Google OAuth app.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Adding Google Auth to Your Wasp File
Let's start by properly configuring the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// 2. Enable Google Auth
// highlight-next-line
google: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
`userEntity` is explained in [the social auth overview](./overview.md#user-entity).
#### 2. Adding the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a Google OAuth App
To use Google as an authentication method, you'll first need to create a Google project and provide Wasp with your client key and secret. Here's how you do it:
1. Create a Google Cloud Platform account if you do not already have one: https://cloud.google.com/
2. Create and configure a new Google project here: https://console.cloud.google.com/projectcreate


3. Search for **Google Auth** in the top bar (1), click on **Google Auth Platform** (2). Then click on **Get Started** (3).


4. Fill out you app information. For the **Audience** field, we will go with **External**. When you're done, click **Create**.


5. You should now be in the **OAuth Overview** page. Click on **Create OAuth Client** (1).

6. Fill out the form. These are the values for a typical Wasp application:
| # | Field | Value |
| - | ------------------------ | -------------------------------------------- |
| 1 | Application type | Web application |
| 2 | Name | (your wasp app name) |
| 3 | Authorized redirect URIs | `http://localhost:3001/auth/google/callback` |
:::note
Once you know on which URL(s) your API server will be deployed, also add those URL(s) to the **Authorized redirect URIs**.\
For example: `https://your-server-url.com/auth/google/callback`
:::

Then click on **Create** (4).
7. You will see a box saying **OAuth client created**. Click on **OK**.

8. Click on the name of your newly-created app.

9. On the right-hand side, you will see your **Client ID** (1) and **Client secret** (2). **Copy them somewhere safe, as you will need them for your app.**

:::info
These are the credentials your app will use to authenticate with Google. Do not share them anywhere publicly, as anyone with these credentials can impersonate your app and access user data.
:::
10. Click on **Data Access** (1) in the left-hand menu, then click on **Add or remove scopes** (2). You should select `userinfo.profile` (3), and optionally `userinfo.email` (4), or any other scopes you want to use. Remember to click **Update** and **Save** when done.


11. Go to **Audience** (1) in the left-hand menu, and add any test users you want (2). This is useful for testing your app before going live. You can add any email addresses you want to test with.

12. Finally, you can go to **Branding** (1) in the left-hand menu, and customize your app's branding in the Google login page. _This is optional_, but recommended if you want to make your app look more professional.

#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
```
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Create the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's now create a `auth.{jsx,tsx}` file in the `src/pages`.
It should have the following code:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
:::info Auth UI
Our pages use an automatically-generated Auth UI component. Read more about Auth UI components [here](../../auth/ui).
:::
#### Conclusion
Yay, we've successfully set up Google Auth! đ

Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Default Behaviour
Add `google: {}` to the `auth.methods` dictionary to use it with default settings:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
google: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From Google
We are using Google's API and its `/userinfo` endpoint to fetch the user's data.
The data received from Google is an object which can contain the following fields:
```json
[
"name",
"given_name",
"family_name",
"email",
"email_verified",
"aud",
"exp",
"iat",
"iss",
"locale",
"picture",
"sub"
]
```
The fields you receive depend on the scopes you request. The default scope is set to `profile` only. If you want to get the user's email, you need to specify the `email` scope in the `configFn` function.
For an up to date info about the data received from Google, please refer to the [Google API documentation](https://developers.google.com/identity/openid-connect/openid-connect#an-id-tokens-payload).
#### Using the Data Received From Google
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/google",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/google"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String
}
// ...
```
```ts title="src/auth/google.ts" auto-js
export const userSignupFields = defineUserSignupFields({
username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name,
})
export function getConfig() {
return {
scopes: ['profile', 'email'],
}
}
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Google ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
google: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/google",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/google"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `google` dict has the following properties:
- #### `configFn: ExtImport`
This function must return an object with the scopes for the OAuth provider.
```ts title="src/auth/google.ts" auto-js
export function getConfig() {
return {
scopes: ['profile', 'email'],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Keycloak
Wasp supports Keycloak Authentication out of the box.
[Keycloak](https://www.keycloak.org/) is an open-source identity and access management solution for modern applications and services. Keycloak provides both SAML and OpenID protocol solutions. It also has a very flexible and powerful administration UI.
Let's walk through enabling Keycloak authentication, explain some of the default settings, and show how to override them.
### Setting up Keycloak Auth
Enabling Keycloak Authentication comes down to a series of steps:
1. Enabling Keycloak authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating a Keycloak client.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Adding Keycloak Auth to Your Wasp File
Let's start by properly configuring the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// 2. Enable Keycloak Auth
// highlight-next-line
keycloak: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
`userEntity` is explained in [the social auth overview](./overview.md#user-entity).
#### 2. Adding the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a Keycloak Client
1. Log into your Keycloak admin console.
2. Under **Clients**, click on **Create Client**.

1. Fill in the **Client ID** and choose a name for the client.

1. In the next step, enable **Client Authentication**.

1. Under **Valid Redirect URIs**, add `http://localhost:3001/auth/keycloak/callback` for local development.

- Once you know on which URL(s) your API server will be deployed, also add those URL(s).
- For example: `https://my-server-url.com/auth/keycloak/callback`.
1. Click **Save**.
2. In the **Credentials** tab, copy the **Client Secret** value, which we'll use in the next step.

#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
KEYCLOAK_CLIENT_ID=your-keycloak-client-id
KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret
KEYCLOAK_REALM_URL=https://your-keycloak-url.com/realms/master
```
We assumed in the `KEYCLOAK_REALM_URL` env variable that you are using the `master` realm. If you are using a different realm, replace `master` with your realm name.
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Create the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's now create an `auth.{jsx,tsx}` file in the `src/pages`.
It should have the following code:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
:::info Auth UI
Our pages use an automatically generated Auth UI component. Read more about Auth UI components [here](../../auth/ui).
:::
#### Conclusion
Yay, we've successfully set up Keycloak Auth!
Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Default Behaviour
Add `keycloak: {}` to the `auth.methods` dictionary to use it with default settings:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
keycloak: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From Keycloak
We are using Keycloak's API and its `/userinfo` endpoint to fetch the user's data.
```ts title="Keycloak user data"
{
sub: '5adba8fc-3ea6-445a-a379-13f0bb0b6969',
email_verified: true,
name: 'Test User',
preferred_username: 'test',
given_name: 'Test',
family_name: 'User',
email: 'test@example.com'
}
```
The fields you receive will depend on the scopes you requested. The default scope is set to `profile` only. If you want to get the user's email, you need to specify the `email` scope in the `configFn` function.
For up-to-date info about the data received from Keycloak, please refer to the [Keycloak API documentation](https://www.keycloak.org/docs-api/23.0.7/javadocs/org/keycloak/representations/UserInfo.html).
#### Using the Data Received From Keycloak
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
keycloak: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/keycloak",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/keycloak"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String
}
// ...
```
```ts title="src/auth/keycloak.ts" auto-js
export const userSignupFields = defineUserSignupFields({
username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name,
})
export function getConfig() {
return {
scopes: ['profile', 'email'],
}
}
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Keycloak ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
keycloak: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/keycloak",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/keycloak"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `keycloak` dict has the following properties:
- #### `configFn: ExtImport`
This function must return an object with the scopes for the OAuth provider.
```ts title="src/auth/keycloak.ts" auto-js
export function getConfig() {
return {
scopes: ['profile', 'email'],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Slack
Wasp supports Slack Authentication out of the box.
Using Slack Authentication is perfect when you build a control panel for a Slack app.
Let's walk through enabling Slack Authentication, explain some quirks, explore default settings and show how to override them.
### Setting up Slack Auth
Enabling Slack Authentication comes down to a series of steps:
1. Enabling Slack authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating Slack App.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Enabling Slack authentication in the Wasp file.
Now let's properly configure the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// highlight-next-line
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// highlight-next-line
// 2. Enable Slack Auth
// highlight-next-line
slack: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
#### 2. Add the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a Slack App
To use Slack as an authentication method, you'll first need to create a Slack App and provide Wasp with your client key and secret. Here's how you do it:
1. Log into your Slack account and navigate to: https://api.slack.com/apps.
2. Select **Create New App**.
3. Click "From scratch"
3. Enter App Name and select workspace that should host your app.
4. Go to the **OAuth & Permissions** tab on the sidebar and click **Add New Redirect URL**.
- Enter the value `https://.local.lt/auth/slack/callback`, where `` is your selected localtunnel subdomain.
- Slack requires us to use HTTPS even when developing, [read below](#slack-https) how to set it up.
4. Hit **Save URLs**.
5. Go to **Basic Information** tab
6. Hit **Show** next to **Client Secret**
6. Copy your Client ID and Client Secret as you'll need them in the next step.
:::tip
Be precise with your redirect URL. Slackâs redirect URLs are case-sensitive and sensitive to trailing slashes.
For example, `https://your-app.loca.lt/auth/slack/callback` and `https://your-app.loca.lt/auth/slack/callback/` are **not** the same.
:::
#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
SLACK_CLIENT_ID=your-slack-client-id
SLACK_CLIENT_SECRET=your-slack-client-secret
```
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Creating the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../../auth/ui).
#### Conclusion
Yay, we've successfully set up Slack Auth! đ

Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Developing with Slack auth and HTTPS {#slack-https}
Unlike most OAuth providers, Slack **requires HTTPS and publicly accessible URL for the OAuth redirect URL**.
This means that we can't simply use `localhost:3001` as a base host for redirect urls. Instead, we need to configure
Wasp server to be publicly available under HTTPS, even in the local development environment.
Fortunately, there are quite a few free and convenient tools available to simplify the process, such as
[localtunnel.me](https://localtunnel.me/) (free) and [ngrok.com](https://ngrok.com) (lots of features,
but free tier is limited).
Install localtunnel globally with `npm install -g localtunnel`.
Start a tunnel with `lt --port 3001 -s `, where `` is a unique subdomain you would like to have.
:::info Subdomain option
Usually localtunnel will assign you a random subdomain on each start, but you can specify it with the `-s` flag.
Doing it this way will make it easier to remember the URL and will also make it easier to set up the redirect URL
in Slack app settings.
:::
After starting the tunnel, you will see your tunnel URL in the terminal. Go to that URL to unlock the tunnel by entering your IP address
in a field that appears on the page the first time you open it in the browser. This is a basic anti-abuse mechanism. If you're not sure
what your IP is, you can find it by running `curl ifconfig.me` or going to [ifconfig.me](https://ifconfig.me).
Now that your server is exposed to the public, we need to configure Wasp to use the new public domain. This needs to be done in two places:
server and client configuration.
To configure client, add this line to your `.env.client` file (create it if doesn't exist):
```bash title=".env.client"
REACT_APP_API_URL=https://.loca.lt
```
Similarly, to configure the server, add this line to your `.env.server`:
```bash title=".env.server"
WASP_SERVER_URL=https://.loca.lt
```
### Default Behaviour
Add `slack: {}` to the `auth.methods` dictionary to use it with default settings.
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
slack: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From Slack
We are using Slack's API and its `/openid.connect.userInfo` endpoint to get the user data.
The data we receive from Slack on the `/openid.connect.userInfo` endpoint looks something like this:
```json
{
"ok": true,
"sub": "U0R7JM",
"https://slack.com/user_id": "U0R7JM",
"https://slack.com/team_id": "T0R7GR",
"email": "krane@slack-corp.com",
"email_verified": true,
"date_email_verified": 1622128723,
"name": "krane",
"picture": "https://secure.gravatar.com/....png",
"given_name": "Bront",
"family_name": "Labradoodle",
"locale": "en-US",
"https://slack.com/team_name": "kraneflannel",
"https://slack.com/team_domain": "kraneflannel",
"https://slack.com/user_image_24": "...",
"https://slack.com/user_image_32": "...",
"https://slack.com/user_image_48": "...",
"https://slack.com/user_image_72": "...",
"https://slack.com/user_image_192": "...",
"https://slack.com/user_image_512": "...",
"https://slack.com/team_image_34": "...",
"https://slack.com/team_image_44": "...",
"https://slack.com/team_image_68": "...",
"https://slack.com/team_image_88": "...",
"https://slack.com/team_image_102": "...",
"https://slack.com/team_image_132": "...",
"https://slack.com/team_image_230": "...",
"https://slack.com/team_image_default": true
}
```
The fields you receive depend on the scopes you request. In the example above, the scope includes `email`, `profile` and `openid`. By default, only `openid` is requested. See below for instructions on how to request additional scopes.
For an up to date info about the data received from Slack, please refer to the [Slack API documentation](https://api.slack.com/methods/openid.connect.userInfo).
#### Using the Data Received From Slack
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
slack: {
// highlight-next-line
configFn: import { config } from "@src/auth/slack",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/slack"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
avatarUrl String
}
// ...
```
```ts title="src/auth/slack.ts" auto-js
export function config() {
console.log('Inside user-supplied Slack config')
return {
scopes: ["openid", "email", "profile"],
}
}
export const userSignupFields = defineUserSignupFields({
username: (data: any) => data.profile.name,
avatarUrl: (data: any) => data.profile.picture,
})
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Slack ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
slack: {
// highlight-next-line
configFn: import { config } from "@src/auth/slack",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/slack"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `slack` dict has the following properties:
- #### `configFn: ExtImport`
This function should return an object with the scopes for the OAuth provider.
```ts title="src/auth/slack.ts" auto-js
export function getConfig() {
return {
scopes: ["openid", "email", "profile"],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Discord
Wasp supports Discord Authentication out of the box.
Letting your users log in using their Discord accounts turns the signup process into a breeze.
Let's walk through enabling Discord Authentication, explain some of the default settings, and show how to override them.
### Setting up Discord Auth
Enabling Discord Authentication comes down to a series of steps:
1. Enabling Discord authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating a Discord App.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Adding Discord Auth to Your Wasp File
Let's start by properly configuring the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// highlight-next-line
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// highlight-next-line
// 2. Enable Discord Auth
// highlight-next-line
discord: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
#### 2. Add the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a Discord App
To use Discord as an authentication method, you'll first need to create a Discord App and provide Wasp with your client key and secret. Here's how you do it:
1. Log into your Discord account and navigate to: https://discord.com/developers/applications.
2. Select **New Application**.
3. Supply required information.
4. Go to the **OAuth2** tab on the sidebar and click **Add Redirect**
- For development, put: `http://localhost:3001/auth/discord/callback`.
- Once you know on which URL your API server will be deployed, you can create a new app with that URL instead e.g. `https://your-server-url.com/auth/discord/callback`.
4. Hit **Save Changes**.
5. Hit **Reset Secret**.
6. Copy your Client ID and Client secret as you'll need them in the next step.
#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
DISCORD_CLIENT_ID=your-discord-client-id
DISCORD_CLIENT_SECRET=your-discord-client-secret
```
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Creating the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's create a `auth.{jsx,tsx}` file in the `src/pages` folder and add the following to it:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](../../auth/ui).
#### Conclusion
Yay, we've successfully set up Discord Auth! đ

Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Default Behaviour
Add `discord: {}` to the `auth.methods` dictionary to use it with default settings.
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
discord: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From Discord
We are using Discord's API and its `/users/@me` endpoint to get the user data.
The data we receive from Discord on the `/users/@me` endpoint looks something like this:
```json
{
"id": "80351110224678912",
"username": "Nelly",
"discriminator": "1337",
"avatar": "8342729096ea3675442027381ff50dfe",
"verified": true,
"flags": 64,
"banner": "06c16474723fe537c283b8efa61a30c8",
"accent_color": 16711680,
"premium_type": 1,
"public_flags": 64,
"avatar_decoration_data": {
"sku_id": "1144058844004233369",
"asset": "a_fed43ab12698df65902ba06727e20c0e"
}
}
```
The fields you receive will depend on the scopes you requested. The default scope is set to `identify` only. If you want to get the email, you need to specify the `email` scope in the `configFn` function.
For an up to date info about the data received from Discord, please refer to the [Discord API documentation](https://discord.com/developers/docs/resources/user#user-object-user-structure).
#### Using the Data Received From Discord
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
discord: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/discord",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/discord"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String
}
// ...
```
```ts title="src/auth/discord.ts" auto-js
export const userSignupFields = defineUserSignupFields({
username: (data: any) => data.profile.global_name,
avatarUrl: (data: any) => data.profile.avatar,
})
export function getConfig() {
return {
scopes: ['identify'],
}
}
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Discord ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
discord: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/discord",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/discord"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `discord` dict has the following properties:
- #### `configFn: ExtImport`
This function should return an object with the scopes for the OAuth provider.
```ts title="src/auth/discord.ts" auto-js
export function getConfig() {
return {
scopes: [],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Microsoft
Wasp supports Microsoft Authentication out of the box.
Microsoft Auth uses [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) as the identity provider. This lets your users sign in with their Microsoft accounts â including personal Microsoft accounts, work/school (Microsoft 365) accounts, or both, depending on your configuration.
Let's walk through enabling Microsoft authentication, explain some of the default settings, and show how to override them.
### Understanding tenants {#understanding-tenants}
When setting up Microsoft Authentication, you'll encounter the concept of "tenants". A tenant represents an organization in Microsoft Entra ID. Depending on the supported account types you choose during app registration, your app will be associated with a specific tenant, or be multi-tenant.
When planning out your Microsoft integration, you have to decide which kind of accounts will be able to sign in. This will determine the tenant ID you use in your configuration, and the users that can sign in to your app.
| Supported Account Types | Tenant ID Value | Description | Best for |
| --- | --- | --- | --- |
| Tenant-specific | Your Tenant ID (generated by Microsoft) | Only users in your Microsoft Entra ID tenant can sign in. | Internal-only apps for a specific organization. |
| Organization accounts | `organizations` | Any user with a "work or school" Microsoft account can sign in, but not personal accounts. | B2B apps targeting users in other organizations. |
| Personal accounts | `consumers` | Only personal Microsoft accounts (e.g., Outlook.com, Hotmail, Xbox) can sign in. | Consumer-facing apps targeting individual users. |
| All of the above | `common` | Both organizational accounts and personal Microsoft accounts can sign in. | Apps that can be used by personal, work, and school accounts. |
We recommend choosing the least-permissive option that fits your use case, as Microsoft might require you to submit to a verification process if you want to use the higher-privileged tenant types in production.
### Setting up Microsoft Auth
Enabling Microsoft Authentication comes down to a series of steps:
1. Enabling Microsoft authentication in the Wasp file.
2. Adding the `User` entity.
3. Creating a Microsoft Entra ID app registration.
4. Adding the necessary Routes and Pages
5. Using Auth UI components in our Pages.
#### 1. Adding Microsoft Auth to Your Wasp File
Let's start by properly configuring the Auth object:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// 1. Specify the User entity (we'll define it next)
// highlight-next-line
userEntity: User,
methods: {
// 2. Enable Microsoft Auth
// highlight-next-line
microsoft: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
`userEntity` is explained in [the social auth overview](./overview.md#user-entity).
#### 2. Adding the User Entity
Let's now define the `app.auth.userEntity` entity in the `schema.prisma` file:
```prisma title="schema.prisma"
// 3. Define the user entity
model User {
// highlight-next-line
id Int @id @default(autoincrement())
// Add your own fields below
// ...
}
```
#### 3. Creating a Microsoft Entra ID App Registration
To use Microsoft as an authentication method, you'll first need to register an application in the Microsoft Entra ID portal and provide Wasp with your client ID, client secret, and tenant ID.
1. Go to the [Microsoft Entra ID portal](https://entra.microsoft.com/). Login or sign up if necessary.
2. In the left sidebar, click on "App registrations" **(1)** and then "New registration" **(2)**.

3. Fill out the form. These are the values for a typical Wasp application:
| # | Field | Value |
| - | ------------------------ | -------------------------------------------- |
| 1 | Name | (your wasp app name) |
| 1 | Supported account types | Read through **[Understanding tenants](#understanding-tenants)** |
| 3 | Authorized redirect URIs | Web: `http://localhost:3001/auth/microsoft/callback` |
:::note
Once you know on which URL(s) your API server will be deployed, also add those URL(s) in the **Authentication** section.\
For example: `https://your-server-url.com/auth/microsoft/callback`
:::

4. You should now see your app registration's overview page. Take note of the **Application (client) ID (1)** and the **Directory (tenant) ID (2)**, as you'll need them in the next steps.

5. Next, go to the "Certificates & secrets" **(1)** page from the left sidebar, and create a new client secret **(2)**. You can fill out the name of your Wasp app as the client secret's name **(3)**.

6. Finally, take note of the client secret's value, as you'll need it in the next steps. Make sure to copy it somewhere safe, as you won't be able to see it again!

:::info
The keys you copied are the credentials your app will use to authenticate with Microsoft. Do not share them anywhere publicly, as anyone with these credentials can impersonate your app and access user data.
:::
#### 4. Adding Environment Variables
Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step):
```bash title=".env.server"
MICROSOFT_TENANT_ID=your-microsoft-tenant-id
MICROSOFT_CLIENT_ID=your-microsoft-client-id
MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret
```
The `MICROSOFT_TENANT_ID` should be set based on the supported account types you chose, as described in the [Understanding Tenant IDs](#understanding-tenants) section above.
#### 5. Adding the Necessary Routes and Pages
Let's define the necessary authentication Routes and Pages.
Add the following code to your `main.wasp` file:
```wasp title="main.wasp"
// ...
route LoginRoute { path: "/login", to: LoginPage }
page LoginPage {
component: import { Login } from "@src/pages/auth"
}
```
We'll define the React components for these pages in the `src/pages/auth.{jsx,tsx}` file below.
#### 6. Create the Client Pages
:::info
We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](../../project/css-frameworks).
:::
Let's now create an `auth.{jsx,tsx}` file in the `src/pages`.
It should have the following code:
```tsx title="src/pages/auth.tsx" auto-js
export function Login() {
return (
)
}
// A layout component to center the content
export function Layout({ children }: { children: ReactNode }) {
return (
{children}
)
}
```
:::info Auth UI
Our pages use an automatically generated Auth UI component. Read more about Auth UI components [here](../../auth/ui).
:::
#### Conclusion
Yay, we've successfully set up Microsoft Auth! đ

Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication.
To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](../../auth/overview).
### Default Behaviour
Add `microsoft: {}` to the `auth.methods` dictionary to use it with default settings:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
// highlight-next-line
microsoft: {}
},
onAuthFailedRedirectTo: "/login"
},
}
```
### Overrides
#### Data Received From Microsoft
We are using Microsoft's Graph API and its `/oidc/userinfo` endpoint to fetch the user's data.
The data received from Microsoft is an object which can contain the following fields:
```json
{
"sub": "OLu859SGc2Sr9ZsqbkG-QbeLgJlb41KcdiPoLYNpSFA",
"name": "Mikah Ollenburg", // all names require the âprofileâ scope.
"family_name": " Ollenburg",
"given_name": "Mikah",
"picture": "https://graph.microsoft.com/v1.0/me/photo/$value",
"email": "mikoll@contoso.com" // requires the âemailâ scope.
}
```
The fields you receive depend on the scopes you request. The default scopes are set to `openid`, `profile`, and `email`.
For up-to-date info about the data received from Microsoft, please refer to the [Microsoft Graph API documentation](https://learn.microsoft.com/en-us/entra/identity-platform/userinfo).
#### Using the Data Received From Microsoft
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
microsoft: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/microsoft",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/microsoft"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
username String @unique
displayName String
}
// ...
```
```ts title="src/auth/microsoft.ts" auto-js
export const userSignupFields = defineUserSignupFields({
username: () => 'hardcoded-username',
displayName: (data: any) => data.profile.name,
})
export function getConfig() {
return {
scopes: ['openid', 'profile', 'email'],
}
}
```
### Using Auth
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), you'll be able to access the user's Microsoft ID like this:
### API Reference
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
userEntity: User,
methods: {
microsoft: {
// highlight-next-line
configFn: import { getConfig } from "@src/auth/microsoft",
// highlight-next-line
userSignupFields: import { userSignupFields } from "@src/auth/microsoft"
}
},
onAuthFailedRedirectTo: "/login"
},
}
```
The `microsoft` dict has the following properties:
- #### `configFn: ExtImport`
This function must return an object with the scopes for the OAuth provider.
```ts title="src/auth/microsoft.ts" auto-js
export function getConfig() {
return {
scopes: ['openid', 'profile', 'email'],
}
}
```
- #### `userSignupFields: ExtImport`
Read more about the `userSignupFields` function [here](../overview#1-defining-extra-fields).
## Create your own UI for Social Auth
[Auth UI](../ui.md) is a common name for all high-level auth forms that come with Wasp.
These include fully functional auto-generated login and signup forms with working social login buttons.
If you're looking for the fastest way to get your auth up and running, that's where you should look.
The UI helpers described below are lower-level and are useful for creating your custom login links.
Wasp provides sign-in buttons and URLs for each of the supported social login providers.
```tsx title="src/LoginPage.tsx" auto-js
GoogleSignInButton,
googleSignInUrl,
GitHubSignInButton,
githubSignInUrl,
} from "wasp/client/auth";
export const LoginPage = () => {
return (
<>
{/* or */}
Sign in with GoogleSign in with GitHub
>
);
};
```
## Accessing User Data
First, we'll check out the most practical info: **how to access the user's data in your app**.
Then, we'll dive into the details of the **auth entities** that Wasp creates behind the scenes to store the user's data. For auth each method, Wasp needs to store different information about the user. For example, username for [Username & password](./username-and-pass) auth, email verification status for [Email](./email) auth, and so on.
We'll also show you how you can use these entities to create a custom signup action.
### Accessing the Auth Fields
When you receive the `user` object [on the client or the server](../overview.md#accessing-the-logged-in-user), it will contain all the user fields you defined in the `User` entity in the `schema.prisma` file. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. In Wasp, this data is called the `AuthUser` object.
#### `AuthUser` Object Fields
All the `User` fields you defined will be present at the top level of the `AuthUser` object. The auth-related fields will be on the `identities` object. For each auth method you enable, there will be a separate data object in the `identities` object.
The `AuthUser` object will change depending on which auth method you have enabled in the Wasp file. For example, if you enabled the email auth and Google auth, it would look something like this:
If the user has only the Google identity, the `AuthUser` object will look like this:
```ts
const user = {
// User data
id: 'cluqs9qyh00007cn73apj4hp7',
address: 'Some address',
// Auth methods specific data
identities: {
email: null,
google: {
id: '1117XXXX1301972049448',
},
},
}
```
If the user has only the email identity, the `AuthUser` object will look like this:
```ts
const user = {
// User data
id: 'cluqsex9500017cn7i2hwsg17',
address: 'Some address',
// Auth methods specific data
identities: {
email: {
id: 'user@app.com',
isEmailVerified: true,
emailVerificationSentAt: '2024-04-08T10:06:02.204Z',
passwordResetSentAt: null,
},
google: null,
},
}
```
In the examples above, you can see the `identities` object contains the `email` and `google` objects. The `email` object contains the email-related data and the `google` object contains the Google-related data.
:::info Make sure to check if the data exists
Before accessing some auth method's data, you'll need to check if that data exists for the user and then access it:
```ts
if (user.identities.google !== null) {
const userId = user.identities.google.id
// ...
}
```
You need to do this because if a user didn't sign up with some auth method, the data for that auth method will be `null`.
:::
Let's look at the data for each of the available auth methods:
- [Username & password](../username-and-pass.md) data
- [Email](../email.md) data
- [Google](../social-auth/google.md) data
- [GitHub](../social-auth/github.md) data
- [Keycloak](../social-auth/keycloak.md) data
- [Discord](../social-auth/discord.md) data
If you support multiple auth methods, you'll need to find which identity exists for the user and then access its data:
```ts
if (user.identities.email !== null) {
const email = user.identities.email.id
// ...
} else if (user.identities.google !== null) {
const googleId = user.identities.google.id
// ...
}
```
#### `getFirstProviderUserId` Helper
The `getFirstProviderUserId` method returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID.
This can be useful if you support multiple authentication methods and you need _any_ ID that identifies the user in your app.
```jsx title="src/MainPage.jsx"
const MainPage = ({ user }) => {
const userId = user.getFirstProviderUserId()
// ...
}
```
```js title="src/tasks.js"
export const createTask = async (args, context) => {
const userId = context.user.getFirstProviderUserId()
// ...
}
```
```tsx title="src/MainPage.tsx"
import { type AuthUser } from 'wasp/auth'
const MainPage = ({ user }: { user: AuthUser }) => {
const userId = user.getFirstProviderUserId()
// ...
}
```
```ts title="src/tasks.ts"
export const createTask: CreateTask<...> = async (args, context) => {
const userId = context.user.getFirstProviderUserId()
// ...
}
```
\* Multiple identities per user will be possible in the future and then the `getFirstProviderUserId` method will return the ID of the first identity that it finds without any guarantees about which one it will be.
### Including the User with Other Entities
Sometimes, you might want to include the user's data when fetching other entities. For example, you might want to include the user's data with the tasks they have created.
We'll mention the `auth` and the `identities` relations which we will explain in more detail later in the [Entities Explained](#entities-explained) section.
:::caution Be careful about sensitive data
You'll need to include the `auth` and the `identities` relations to get the full auth data about the user. However, you should keep in mind that the `providerData` field in the `identities` can contain sensitive data like the user's hashed password (in case of email or username auth), so you will likely want to exclude it if you are returning those values to the client.
:::
You can include the full user's data with other entities using the `include` option in the Prisma queries:
```js title="src/tasks.js"
export const getAllTasks = async (args, context) => {
return context.entities.Task.findMany({
orderBy: { id: 'desc' },
select: {
id: true,
title: true,
// highlight-next-line
user: {
include: {
// highlight-next-line
auth: {
include: {
// highlight-next-line
identities: {
// Including only the `providerName` and `providerUserId` fields
select: {
providerName: true,
providerUserId: true,
},
},
},
},
},
},
},
})
}
```
```ts title="src/tasks.ts"
export const getAllTasks = (async (args, context) => {
return context.entities.Task.findMany({
orderBy: { id: 'desc' },
select: {
id: true,
title: true,
// highlight-next-line
user: {
include: {
// highlight-next-line
auth: {
include: {
// highlight-next-line
identities: {
// Including only the `providerName` and `providerUserId` fields
select: {
providerName: true,
providerUserId: true,
},
},
},
},
},
},
},
})
}) satisfies tasks.GetAllQuery<{}, {}>
```
If you have some **piece of the auth data that you want to access frequently** (for example the `username`), it's best to store it at the top level of the `User` entity.
For example, save the `username` or `email` as a property on the `User` and you'll be able to access it without including the `auth` and `identities` fields. We show an example in the [Defining Extra Fields on the User Entity](../overview.md#1-defining-extra-fields) section of the docs.
#### Getting Auth Data from the User Object
When you have the `user` object with the `auth` and `identities` fields, it can be a bit tedious to obtain the auth data (like username or Google ID) from it:
```jsx title="src/MainPage.jsx"
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {task.user.auth?.identities[0].providerUserId}
{task.title} by {task.user.auth?.identities[0].providerUserId}
))}
)
}
```
Wasp offers a few helper methods to access the user's auth data when you retrieve the `user` like this. They are `getUsername`, `getEmail` and `getFirstProviderUserId`. They can be used both on the client and the server.
##### `getUsername`
It accepts the `user` object and if the user signed up with the [Username & password](./username-and-pass) auth method, it returns the username or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included.
```jsx title="src/MainPage.jsx"
import { getUsername } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getUsername(task.user)}
))}
)
}
```
```tsx title="src/MainPage.tsx"
import { getUsername } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getUsername(task.user)}
))}
)
}
```
##### `getEmail`
It accepts the `user` object and if the user signed up with the [Email](./email) auth method, it returns the email or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included.
```jsx title="src/MainPage.jsx"
import { getEmail } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getEmail(task.user)}
))}
)
}
```
```tsx title="src/MainPage.tsx"
import { getEmail } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getEmail(task.user)}
))}
)
}
```
##### `getFirstProviderUserId`
It returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID. The `user` object needs to have the `auth` and the `identities` relations included.
```jsx title="src/MainPage.jsx"
import { getFirstProviderUserId } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getFirstProviderUserId(task.user)}
))}
)
}
```
```tsx title="src/MainPage.tsx"
import { getFirstProviderUserId } from 'wasp/auth'
function MainPage() {
// ...
return (
{tasks.map((task) => (
{task.title} by {getFirstProviderUserId(task.user)}
))}
)
}
```
### Entities Explained
To store user's auth information, Wasp does a few things behind the scenes. Wasp takes your `schema.prisma` file and combines it with additional entities to create the final `schema.prisma` file that is used in your app.
In this section, we will explain which entities are created and how they are connected.
#### User Entity
When you want to add authentication to your app, you need to specify the `userEntity` field.
For example, you might set it to `User`:
```wasp title="main.wasp"
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
auth: {
// highlight-next-line
userEntity: User,
// ...
},
}
```
And define the `User` in the `schema.prisma` file:
```prisma title="schema.prisma"
model User {
id Int @id @default(autoincrement())
// Any other fields you want to store about the user
}
```
The `User` entity is a "business logic user" which represents a user of your app.
You can use this entity to store any information about the user that you want to store. For example, you might want to store the user's name or address.
You can also use the user entity to define the relations between users and other entities in your app. For example, you might want to define a relation between a user and the tasks that they have created.
You **own** the user entity and you can modify it as you wish. You can add new fields to it, remove fields from it, or change the type of the fields. You can also add new relations to it or remove existing relations from it.
On the other hand, the `Auth`, `AuthIdentity` and `Session` entities are created behind the scenes and are used to store the user's login credentials. You as the developer don't need to care about this entity most of the time. Wasp **owns** these entities.
In the case you want to create a custom signup action, you will need to use the `Auth` and `AuthIdentity` entities directly.
#### Example App Model
Let's imagine we created a simple tasks management app:
- The app has email and Google-based auth.
- Users can create tasks and see the tasks that they have created.
Let's look at how would that look in the database:
If we take a look at an example user in the database, we can see:
- The business logic user, `User` is connected to multiple `Task` entities.
- In this example, "Example User" has two tasks.
- The `User` is connected to exactly one `Auth` entity.
- Each `Auth` entity can have multiple `AuthIdentity` entities.
- In this example, the `Auth` entity has two `AuthIdentity` entities: one for the email-based auth and one for the Google-based auth.
- Each `Auth` entity can have multiple `Session` entities.
- In this example, the `Auth` entity has one `Session` entity.
#### `Auth` Entity Internal!
Wasp's internal `Auth` entity is used to connect the business logic user, `User` with the user's login credentials.
```prisma
model Auth {
id String @id @default(uuid())
userId Int? @unique
// Wasp injects this relation on the User entity as well
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
identities AuthIdentity[]
sessions Session[]
}
```
The `Auth` fields:
- `id` is a unique identifier of the `Auth` entity.
- `userId` is a foreign key to the `User` entity.
- It is used to connect the `Auth` entity with the business logic user.
- `user` is a relation to the `User` entity.
- This relation is injected on the `User` entity as well.
- `identities` is a relation to the `AuthIdentity` entity.
- `sessions` is a relation to the `Session` entity.
#### `AuthIdentity` Entity Internal!
The `AuthIdentity` entity is used to store the user's login credentials for various authentication methods.
```prisma
model AuthIdentity {
providerName String
providerUserId String
providerData String @default("{}")
authId String
auth Auth @relation(fields: [authId], references: [id], onDelete: Cascade)
@@id([providerName, providerUserId])
}
```
The `AuthIdentity` fields:
- `providerName` is the name of the authentication provider.
- For example, `email` or `google`.
- `providerUserId` is the user's ID in the authentication provider.
- For example, the user's email or Google ID.
- `providerData` is a JSON string that contains additional data about the user from the authentication provider.
- For example, for password based auth, this field contains the user's hashed password.
- This field is a `String` and not a `Json` type because [Prisma doesn't support the `Json` type for SQLite](https://github.com/prisma/prisma/issues/3786).
- `authId` is a foreign key to the `Auth` entity.
- It is used to connect the `AuthIdentity` entity with the `Auth` entity.
- `auth` is a relation to the `Auth` entity.
#### `Session` Entity Internal!
The `Session` entity is used to store the user's session information. It is used to keep the user logged in between page refreshes.
```prisma
model Session {
id String @id @unique
expiresAt DateTime
userId String
auth Auth @relation(references: [id], fields: [userId], onDelete: Cascade)
@@index([userId])
}
```
The `Session` fields:
- `id` is a unique identifier of the `Session` entity.
- `expiresAt` is the date when the session expires.
- `userId` is a foreign key to the `Auth` entity.
- It is used to connect the `Session` entity with the `Auth` entity.
- `auth` is a relation to the `Auth` entity.
### Custom Signup Action
Let's take a look at how you can use the `Auth` and `AuthIdentity` entities to create custom login and signup actions. For example, you might want to create a custom signup action that creates a user in your app and also creates a user in a third-party service.
:::info Custom Signup Examples
In the Advanced section you can see an example for [Email](../advanced/custom-auth-actions.md#email) or [Username and password](../advanced/custom-auth-actions.md#username-and-password) authentication.
:::
Below is a simplified version of a custom signup action which you probably wouldn't use in your app but it shows you how you can use the `Auth` and `AuthIdentity` entities to create a custom signup action.
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup",
entities: [User]
}
```
```js title="src/auth/signup.js"
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'
export const signup = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{}
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
```wasp title="main.wasp"
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup",
entities: [User]
}
```
```ts title="src/auth/signup.ts"
import {
createProviderId,
sanitizeAndSerializeProviderData,
createUser,
} from 'wasp/server/auth'
import type { CustomSignup } from 'wasp/server/operations'
type CustomSignupInput = {
username: string
password: string
}
type CustomSignupOutput = {
success: boolean
message: string
}
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, { entities: { User } }) => {
try {
// Provider ID is a combination of the provider name and the provider user ID
// And it is used to uniquely identify the user in your app
const providerId = createProviderId('username', args.username)
// sanitizeAndSerializeProviderData hashes the password and returns a JSON string
const providerData = await sanitizeAndSerializeProviderData<'username'>({
hashedPassword: args.password,
})
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{}
)
// This is equivalent to:
// await User.create({
// data: {
// auth: {
// create: {
// identities: {
// create: {
// providerName: 'username',
// providerUserId: args.username
// providerData,
// },
// },
// }
// },
// }
// })
} catch (e) {
return {
success: false,
message: e.message,
}
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: 'User created successfully',
}
}
```
You can use whichever method suits your needs better: either the `createUser` function or Prisma's `User.create` method. The `createUser` function is a bit more convenient to use because it hides some of the complexity. On the other hand, the `User.create` method gives you more control over the data that is stored in the `Auth` and `AuthIdentity` entities.
## Auth Hooks
Auth hooks allow you to "hook into" the auth process at various stages and run your custom code. For example, if you want to forbid certain emails from signing up, or if you wish to send a welcome email to the user after they sign up, auth hooks are the way to go.
### Supported hooks
The following auth hooks are available in Wasp:
- [`onBeforeSignup`](#executing-code-before-the-user-signs-up)
- [`onAfterSignup`](#executing-code-after-the-user-signs-up)
- [`onAfterEmailVerified`](#executing-code-after-a-user-verifies-their-email)
- [`onBeforeOAuthRedirect`](#executing-code-before-the-oauth-redirect)
- [`onBeforeLogin`](#executing-code-before-the-user-logs-in)
- [`onAfterLogin`](#executing-code-after-the-user-logs-in)
We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the auth flows:
\* When using the OAuth auth providers, the login hooks are both called before the session is created but the session is created quickly afterward, so it shouldn't make any difference in practice.
Users registering with [email](./email.md) must verify it before they can log in. This verification triggers the Email verification flow:
Users signing in with [OAuth](./social-auth/overview.md) must authorize access before completing login. This authorization triggers the OAuth consent flow:
### Using hooks
To use auth hooks, you must first declare them in the Wasp file:
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
auth: {
userEntity: User,
methods: {
...
},
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
auth: {
userEntity: User,
methods: {
...
},
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
If the hooks are defined as async functions, Wasp _awaits_ them. This means the auth process waits for the hooks to finish before continuing.
Wasp ignores the hooks' return values. The only exception is the `onBeforeOAuthRedirect` hook, whose return value affects the OAuth redirect URL.
We'll now go through each of the available hooks.
#### Executing code before the user signs up
Wasp calls the `onBeforeSignup` hook before the user is created.
The `onBeforeSignup` hook can be useful if you want to reject a user based on some criteria before they sign up.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
import { HttpError } from 'wasp/server'
export const onBeforeSignup = async ({ providerId, prisma, req }) => {
const count = await prisma.user.count()
console.log('number of users before', count)
console.log('provider name', providerId.providerName)
console.log('provider user ID', providerId.providerUserId)
if (count > 100) {
throw new HttpError(403, 'Too many users')
}
if (
providerId.providerName === 'email' &&
providerId.providerUserId === 'some@email.com'
) {
throw new HttpError(403, 'This email is not allowed')
}
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
import { HttpError } from 'wasp/server'
import type { OnBeforeSignupHook } from 'wasp/server/auth'
export const onBeforeSignup: OnBeforeSignupHook = async ({
providerId,
prisma,
req,
}) => {
const count = await prisma.user.count()
console.log('number of users before', count)
console.log('provider name', providerId.providerName)
console.log('provider user ID', providerId.providerUserId)
if (count > 100) {
throw new HttpError(403, 'Too many users')
}
if (
providerId.providerName === 'email' &&
providerId.providerUserId === 'some@email.com'
) {
throw new HttpError(403, 'This email is not allowed')
}
}
```
Read more about the data the `onBeforeSignup` hook receives in the [API Reference](#the-onbeforesignup-hook).
#### Executing code after the user signs up
Wasp calls the `onAfterSignup` hook after the user is created.
The `onAfterSignup` hook can be useful if you want to send the user a welcome email or perform some other action after the user signs up like syncing the user with a third-party service.
Since the `onAfterSignup` hook receives the OAuth tokens, you can use this hook to store the OAuth access token and/or [refresh token](#refreshing-the-oauth-access-token) in your database.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
export const onAfterSignup = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
const count = await prisma.user.count()
console.log('number of users after', count)
console.log('user object', user)
// If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId
if (oauth) {
console.log('accessToken', oauth.tokens.accessToken)
console.log('uniqueRequestId', oauth.uniqueRequestId)
const id = oauth.uniqueRequestId
const data = someKindOfStore.get(id)
if (data) {
console.log('saved data for the ID', data)
}
someKindOfStore.delete(id)
}
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
import type { OnAfterSignupHook } from 'wasp/server/auth'
export const onAfterSignup: OnAfterSignupHook = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
const count = await prisma.user.count()
console.log('number of users after', count)
console.log('user object', user)
// If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId
if (oauth) {
console.log('accessToken', oauth.tokens.accessToken)
console.log('uniqueRequestId', oauth.uniqueRequestId)
const id = oauth.uniqueRequestId
const data = someKindOfStore.get(id)
if (data) {
console.log('saved data for the ID', data)
}
someKindOfStore.delete(id)
}
}
```
Read more about the data the `onAfterSignup` hook receives in the [API Reference](#the-onaftersignup-hook).
#### Executing code after a user verifies their email
Wasp calls the `onAfterEmailVerified` hook exactly once, after the user verifies their email.
The `onAfterEmailVerified` hook is useful for triggering actions in response to the verification event â such as sending a welcome email or syncing user data with a third-party service.
The `onAfterEmailVerified` hook receives an `email` string and `user` object, this makes it easy to perform personalized actions upon email verification.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
export const onAfterEmailVerified = async ({ email }) => {
const info = await emailSender.send({
from: {
name: 'John Doe',
email: 'john@doe.com',
},
to: email,
subject: 'Thank you for verifying your email!',
text: `Your email ${email} has been successfully verified!`,
})
// ...
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
export const onAfterEmailVerified: OnAfterEmailVerifiedHook = async ({
email,
}) => {
const info = await emailSender.send({
from: {
name: 'John Doe',
email: 'john@doe.com',
},
to: email,
subject: 'Thank you for verifying your email!',
text: `Your email ${email} has been successfully verified!`,
})
// ...
}
```
Read more about the data the `onAfterEmailVerified` hook receives in the [API Reference](#the-onafteremailverified-hook).
#### Executing code before the OAuth redirect
Wasp calls the `onBeforeOAuthRedirect` hook after the OAuth redirect URL is generated but before redirecting the user. This hook can access the request object sent from the client at the start of the OAuth process.
The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that you can use later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` or `onAfterLogin` hooks.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
export const onBeforeOAuthRedirect = async ({ url, oauth, prisma, req }) => {
console.log('query params before oAuth redirect', req.query)
// Saving query params for later use in onAfterSignup or onAfterLogin hooks
const id = oauth.uniqueRequestId
someKindOfStore.set(id, req.query)
return { url }
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth'
export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({
url,
oauth,
prisma,
req,
}) => {
console.log('query params before oAuth redirect', req.query)
// Saving query params for later use in onAfterSignup or onAfterLogin hooks
const id = oauth.uniqueRequestId
someKindOfStore.set(id, req.query)
return { url }
}
```
This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider.
Read more about the data the `onBeforeOAuthRedirect` hook receives in the [API Reference](#the-onbeforeoauthredirect-hook).
#### Executing code before the user logs in
Wasp calls the `onBeforeLogin` hook before the user is logged in.
The `onBeforeLogin` hook can be useful if you want to reject a user based on some criteria before they log in.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
import { HttpError } from 'wasp/server'
export const onBeforeLogin = async ({ providerId, user, prisma, req }) => {
if (
providerId.providerName === 'email' &&
providerId.providerUserId === 'some@email.com'
) {
throw new HttpError(403, 'You cannot log in with this email')
}
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
import { HttpError } from 'wasp/server'
import type { OnBeforeLoginHook } from 'wasp/server/auth'
export const onBeforeLogin: OnBeforeLoginHook = async ({
providerId,
user,
prisma,
req,
}) => {
if (
providerId.providerName === 'email' &&
providerId.providerUserId === 'some@email.com'
) {
throw new HttpError(403, 'You cannot log in with this email')
}
}
```
Read more about the data the `onBeforeLogin` hook receives in the [API Reference](#the-onbeforelogin-hook).
#### Executing code after the user logs in
Wasp calls the `onAfterLogin` hook after the user logs in.
The `onAfterLogin` hook can be useful if you want to perform some action after the user logs in, like syncing the user with a third-party service.
Since the `onAfterLogin` hook receives the OAuth tokens, you can use it to update the OAuth access token for the user in your database. You can also use it to [refresh the OAuth access token](#refreshing-the-oauth-access-token) if the provider supports it.
Works with
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
```js title="src/auth/hooks.js"
export const onAfterLogin = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
console.log('user object', user)
// If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId
if (oauth) {
console.log('accessToken', oauth.tokens.accessToken)
console.log('uniqueRequestId', oauth.uniqueRequestId)
const id = oauth.uniqueRequestId
const data = someKindOfStore.get(id)
if (data) {
console.log('saved data for the ID', data)
}
someKindOfStore.delete(id)
}
}
```
```wasp title="main.wasp"
app myApp {
...
auth: {
...
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
```ts title="src/auth/hooks.ts"
import type { OnAfterLoginHook } from 'wasp/server/auth'
export const onAfterLogin: OnAfterLoginHook = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
console.log('user object', user)
// If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId
if (oauth) {
console.log('accessToken', oauth.tokens.accessToken)
console.log('uniqueRequestId', oauth.uniqueRequestId)
const id = oauth.uniqueRequestId
const data = someKindOfStore.get(id)
if (data) {
console.log('saved data for the ID', data)
}
someKindOfStore.delete(id)
}
}
```
Read more about the data the `onAfterLogin` hook receives in the [API Reference](#the-onafterlogin-hook).
#### Refreshing the OAuth access token
Some OAuth providers support refreshing the access token when it expires. To refresh the access token, you need the OAuth **refresh token**.
Wasp exposes the OAuth refresh token in the `onAfterSignup` and `onAfterLogin` hooks. You can store the refresh token in your database and use it to refresh the access token when it expires.
Import the provider object with the OAuth client from the `wasp/server/auth` module. For example, to refresh the Google OAuth access token, import the `google` object from the `wasp/server/auth` module. You use the `refreshAccessToken` method of the OAuth client to refresh the access token.
Here's an example of how you can refresh the access token for Google OAuth:
```js title="src/auth/hooks.js"
import { google } from 'wasp/server/auth'
export const onAfterLogin = async ({ oauth }) => {
if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) {
const newTokens = await google.oAuthClient.refreshAccessToken(
oauth.tokens.refreshToken
)
log('new tokens', newTokens)
}
}
```
```ts title="src/auth/hooks.ts"
import type { OnAfterLoginHook } from 'wasp/server/auth'
import { google } from 'wasp/server/auth'
export const onAfterLogin: OnAfterLoginHook = async ({ oauth }) => {
if (oauth.provider === 'google' && oauth.tokens.refreshToken !== null) {
const newTokens = await google.oAuthClient.refreshAccessToken(
oauth.tokens.refreshToken
)
log('new tokens', newTokens)
}
}
```
Google exposes the `accessTokenExpiresAt` field in the `oauth.tokens` object. You can use this field to determine when the access token expires.
If you want to refresh the token periodically, use a [Wasp Job](../advanced/jobs.md).
### API Reference
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
auth: {
userEntity: User,
methods: {
...
},
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
auth: {
userEntity: User,
methods: {
...
},
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
onAfterSignup: import { onAfterSignup } from "@src/auth/hooks",
onAfterEmailVerified: import { onAfterEmailVerified } from "@src/auth/hooks",
onBeforeOAuthRedirect: import { onBeforeOAuthRedirect } from "@src/auth/hooks",
onBeforeLogin: import { onBeforeLogin } from "@src/auth/hooks",
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks",
},
}
```
#### Common hook input
The following properties are available in all auth hooks:
- `prisma: PrismaClient`
The Prisma client instance which you can use to query your database.
- `req: Request`
The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc.
#### The `onBeforeSignup` hook
```js title="src/auth/hooks.js"
export const onBeforeSignup = async ({ providerId, prisma, req }) => {
// Hook code goes here
}
```
```ts title="src/auth/hooks.ts"
import type { OnBeforeSignupHook } from 'wasp/server/auth'
export const onBeforeSignup: OnBeforeSignupHook = async ({
providerId,
prisma,
req,
}) => {
// Hook code goes here
}
```
The hook receives an object as **input** with the following properties:
- [`providerId: ProviderId`](#providerid-fields)
- Plus the [common hook input](#common-hook-input)
Wasp ignores this hook's **return value**.
#### The `onAfterSignup` hook
```js title="src/auth/hooks.js"
export const onAfterSignup = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
// Hook code goes here
}
```
```ts title="src/auth/hooks.ts"
import type { OnAfterSignupHook } from 'wasp/server/auth'
export const onAfterSignup: OnAfterSignupHook = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
// Hook code goes here
}
```
The hook receives an object as **input** with the following properties:
- [`providerId: ProviderId`](#providerid-fields)
- `user: User`
The user object that was created.
- [`oauth?: OAuthFields`](#oauth-fields)
- Plus the [common hook input](#common-hook-input)
Wasp ignores this hook's **return value**.
#### The `onAfterEmailVerified` hook
```js title="src/auth/hooks.js"
export const onAfterEmailVerified = async ({ email, user, prisma, req }) => {
// Hook code goes here
}
```
```ts title="src/auth/hooks.ts"
export const onAfterEmailVerified: OnAfterEmailVerifiedHook = async ({
email,
user,
prisma,
req,
}) => {
// Hook code goes here
}
```
The hook receives an object as **input** with the following properties:
- `email: string`
The user's veriried email address.
- `user: User`
The user who completed email verification.
- Plus the [common hook input](#common-hook-input)
Wasp ignores this hook's **return value**.
#### The `onBeforeOAuthRedirect` hook
```js title="src/auth/hooks.js"
export const onBeforeOAuthRedirect = async ({ url, oauth, prisma, req }) => {
// Hook code goes here
return { url }
}
```
```ts title="src/auth/hooks.ts"
import type { OnBeforeOAuthRedirectHook } from 'wasp/server/auth'
export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({
url,
oauth,
prisma,
req,
}) => {
// Hook code goes here
return { url }
}
```
The hook receives an object as **input** with the following properties:
- `url: URL`
Wasp uses the URL for the OAuth redirect.
- `oauth: { uniqueRequestId: string }`
The `oauth` object has the following fields:
- `uniqueRequestId: string`
The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.)
You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` or `onAfterLogin` hooks.
- Plus the [common hook input](#common-hook-input)
This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider.
#### The `onBeforeLogin` hook
```js title="src/auth/hooks.js"
export const onBeforeLogin = async ({ providerId, prisma, req }) => {
// Hook code goes here
}
```
```ts title="src/auth/hooks.ts"
import type { OnBeforeLoginHook } from 'wasp/server/auth'
export const onBeforeLogin: OnBeforeLoginHook = async ({
providerId,
prisma,
req,
}) => {
// Hook code goes here
}
```
The hook receives an object as **input** with the following properties:
- [`providerId: ProviderId`](#providerid-fields)
- `user: User`
The user that is trying to log in.
- Plus the [common hook input](#common-hook-input)
Wasp ignores this hook's **return value**.
#### The `onAfterLogin` hook
```js title="src/auth/hooks.js"
export const onAfterLogin = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
// Hook code goes here
}
```
```ts title="src/auth/hooks.ts"
import type { OnAfterLoginHook } from 'wasp/server/auth'
export const onAfterLogin: OnAfterLoginHook = async ({
providerId,
user,
oauth,
prisma,
req,
}) => {
// Hook code goes here
}
```
The hook receives an object as **input** with the following properties:
- [`providerId: ProviderId`](#providerid-fields)
- `user: User`
The logged-in user's object.
- [`oauth?: OAuthFields`](#oauth-fields)
- Plus the [common hook input](#common-hook-input)
Wasp ignores this hook's **return value**.
#### ProviderId fields
The `providerId` object represents the user for the current authentication method. Wasp passes it to the `onBeforeSignup`, `onAfterSignup`, `onBeforeLogin`, and `onAfterLogin` hooks.
It has the following fields:
- `providerName: string`
The provider's name (e.g. `'email'`, `'google'`, `'github`)
- `providerUserId: string`
The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID)
#### OAuth fields
Wasp passes the `oauth` object to the `onAfterSignup` and `onAfterLogin` hooks only when the user is authenticated with [Social Auth](./social-auth/overview.md).
It has the following fields:
- `providerName: string`
The name of the OAuth provider the user authenticated with (e.g. `'google'`, `'github'`).
- `tokens: Tokens`
You can use the OAuth tokens to make requests to the provider's API on the user's behalf.
Depending on the OAuth provider, the `tokens` object might have different fields. For example, Google has the fields `accessToken`, `refreshToken`, `idToken`, and `accessTokenExpiresAt`.
To access the provider-specific fields, you must first narrow down the `oauth.tokens` object type to the specific OAuth provider type.
```ts
if (oauth && oauth.providerName === 'google') {
console.log(oauth.tokens.accessToken)
// ^ Google specific tokens are available here
console.log(oauth.tokens.refreshToken)
console.log(oauth.tokens.idToken)
console.log(oauth.tokens.accessTokenExpiresAt)
}
```
- `uniqueRequestId: string`
The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.)
You can use the unique request ID to get the data that was saved in the `onBeforeOAuthRedirect` hook.
## custom-auth-actions
## Custom sign-up actions
If you need to deeply hook into the sign-up process, you can create your own sign-up action and customize the code to, for example, add extra validation, store more data, or otherwise call custom code at registration time.
:::danger
Custom sign-up actions are complex, and we don't recommend creating a custom sign-up action unless you have a good reason to do so.
They also require you to be careful, as any small mistake will compromise the security of your app.
Before using custom actions, check if our support for [custom auth UI](../overview.md#custom-auth-ui) and for [auth hooks](../auth-hooks.md) could fit well with you requirements.
:::
You are not able to use Wasp UI with custom sign-up actions, so you're expected to implemented your own UI and call the custom actions you create from it.
### Example code
Below you will find a starting point for creating your own actions. The given implementation is similar to what Wasp does under the hood, and it is up to you to customize it.
#### Email
```wasp title="main.wasp"
app myApp {
// ...
auth: {
// ...
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
},
}
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup",
}
```
```ts title="src/auth/hooks.ts" auto-js
// This disables Wasp's default sign-up action
export const onBeforeSignup = async () => {
throw new HttpError(403, 'This sign-up method is disabled')
}
```
```ts title="src/auth/signup.ts" auto-js
createEmailVerificationLink,
createProviderId,
createUser,
ensurePasswordIsPresent,
ensureValidEmail,
ensureValidPassword,
findAuthIdentity,
getProviderData,
sanitizeAndSerializeProviderData,
sendEmailVerificationEmail,
} from "wasp/server/auth";
type CustomSignupInput = {
email: string;
password: string;
};
type CustomSignupOutput = {
success: boolean;
message: string;
};
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, _context) => {
ensureValidEmail(args);
ensurePasswordIsPresent(args);
ensureValidPassword(args);
try {
const providerId = createProviderId("email", args.email);
const existingAuthIdentity = await findAuthIdentity(providerId);
let providerData;
if (existingAuthIdentity) {
// User already exists, handle accordingly
// For example, throw an error or return a message
throw new HttpError(400, "Email already exists.");
// Or, another example, you can check if the user is already
// verified and re-send the verification email if not
providerData = getProviderData<"email">(
existingAuthIdentity.providerData,
);
if (providerData.isEmailVerified)
throw new HttpError(400, "Email already verified.");
}
if (!providerData) {
providerData = await sanitizeAndSerializeProviderData<"email">({
// The provider will hash the password for us, so we don't need to do it here.
hashedPassword: args.password,
isEmailVerified: false,
emailVerificationSentAt: null,
passwordResetSentAt: null,
});
await createUser(
providerId,
providerData,
// Any additional data you want to store on the User entity
{},
);
}
// Verification link links to a client route e.g. /email-verification
const verificationLink = await createEmailVerificationLink(
args.email,
"/email-verification",
);
try {
await sendEmailVerificationEmail(args.email, {
from: {
name: "My App Postman",
email: "hello@itsme.com",
},
to: args.email,
subject: "Verify your email",
text: `Click the link below to verify your email: ${verificationLink}`,
html: `
Click the link below to verify your email
Verify email
`,
});
} catch (e: unknown) {
console.error("Failed to send email verification email:", e);
throw new HttpError(500, "Failed to send email verification email.");
}
} catch (e: any) {
return {
success: false,
message: e.message,
};
}
// Your custom code after sign-up.
// ...
return {
success: true,
message: "User created successfully",
};
};
```
#### Username and password
```wasp title="main.wasp"
app myApp {
// ...
auth: {
// ...
onBeforeSignup: import { onBeforeSignup } from "@src/auth/hooks",
},
}
// ...
action customSignup {
fn: import { signup } from "@src/auth/signup",
}
```
```ts title="src/auth/hooks.ts" auto-js
// This disables Wasp's default sign-up action
export const onBeforeSignup = async () => {
throw new HttpError(403, 'This sign-up method is disabled')
}
```
```ts title="src/auth/signup.ts" auto-js
createProviderId,
createUser,
ensurePasswordIsPresent,
ensureValidPassword,
ensureValidUsername,
sanitizeAndSerializeProviderData,
} from "wasp/server/auth";
type CustomSignupInput = {
username: string;
password: string;
};
type CustomSignupOutput = {
success: boolean;
message: string;
};
export const signup: CustomSignup<
CustomSignupInput,
CustomSignupOutput
> = async (args, _context) => {
ensureValidUsername(args);
ensurePasswordIsPresent(args);
ensureValidPassword(args);
try {
const providerId = createProviderId("username", args.username);
const providerData = await sanitizeAndSerializeProviderData<"username">({
// The provider will hash the password for us, so we don't need to do it here.
hashedPassword: args.password,
});
await createUser(providerId, providerData, {});
} catch (e: any) {
console.error("Error creating user:", e);
return {
success: false,
message: e.message,
};
}
return {
success: true,
message: "User created successfully",
};
};
```
### Validators API Reference
We suggest using the built-in field validators for your authentication flow. You can import them from `wasp/server/auth`. These are the same validators that Wasp uses internally for the default authentication flow.
##### Username
- `ensureValidUsername(args)`
Checks if the username is valid and throws an error if it's not. Read more about the validation rules [here](../overview.md#default-validations).
##### Email
- `ensureValidEmail(args)`
Checks if the email is valid and throws an error if it's not. Read more about the validation rules [here](../overview.md#default-validations).
##### Password
- `ensurePasswordIsPresent(args)`
Checks if the password is present and throws an error if it's not.
- `ensureValidPassword(args)`
Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](../overview.md#default-validations).
------
# Project Setup
## Starter Templates
We created a few starter templates to help you get started with Wasp. Check out the list [below](#available-templates).
### Using a Template
Run `wasp new` to run the interactive mode for creating a new Wasp project.
It will ask you for the project name, and then for the template to use:
```
$ wasp new
Enter the project name (e.g. my-project) ⸠MyFirstProject
Choose a starter template
[1] basic (default)
A basic starter template designed to help you get up and running quickly.
It features examples covering the most common use cases.
[2] minimal
A minimal starter template that features just a single page.
[3] saas
Everything a SaaS needs! Comes with Auth, ChatGPT API, Tailwind, Stripe payments and more.
Check out https://opensaas.sh/ for more details.
[4] ai-generated
đ¤ Describe an app in a couple of sentences and have Wasp AI generate initial code for you. (experimental)
⸠1
đ --- Creating your project from the "basic" template... -------------------------
Created new Wasp app in ./MyFirstProject directory!
To run your new app, do:
cd MyFirstProject
wasp db migrate-dev
wasp start
```
### Available Templates
When you have a good idea for a new product, you don't want to waste your time on setting up common things like authentication, database, etc. That's why we created a few starter templates to help you get started with Wasp.
#### OpenSaaS.sh template

Everything a SaaS needs! Comes with Auth, ChatGPT API, Tailwind, Stripe payments and more. Check out https://opensaas.sh/ for more details.
**Features:** Stripe Payments, OpenAI GPT API, Google Auth, SendGrid, Tailwind, & Cron Jobs
Use this template:
```
wasp new -t saas
```
#### Minimal Template
A minimal starter template that features just a single page. Perfect for starting from scratch with the bare essentials.
Use this template:
```
wasp new -t minimal
```
#### AI Generated Starter đ¤
Using the same tech as used on https://usemage.ai/, Wasp generates your custom starter template based on your
project description. It will automatically generate your data model, auth, queries, actions and React pages.
_You will need to provide your own OpenAI API key to be able to use this template._
**Features:** Generated using OpenAI's GPT models, Auth (username/password), Queries, Actions, Pages, Full-stack Type Safety
## Customizing the App
Each Wasp project can have only one `app` type declaration. It is used to configure your app and its components.
```wasp
app todoApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "ToDo App",
head: [
""
]
}
```
We'll go through some common customizations you might want to do to your app. For more details on each of the fields, check out the [API Reference](#api-reference).
#### Changing the App Title
You may want to change the title of your app, which appears in the browser tab, next to the favicon. You can change it by changing the `title` field of your `app` declaration:
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "BookFace"
}
```
#### Adding Additional Lines to the Head
If you are looking to add additional style sheets or scripts to your app, you can do so by adding them to the `head` field of your `app` declaration.
An example of adding extra style sheets and scripts:
```wasp
app myApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "My App",
head: [ // optional
"",
"",
""
]
}
```
### API Reference
```wasp
app todoApp {
wasp: {
version: "{latestWaspVersion}"
},
title: "ToDo App",
head: [
""
],
auth: {
// ...
},
client: {
// ...
},
server: {
// ...
},
db: {
// ...
},
emailSender: {
// ...
},
webSocket: {
// ...
}
}
```
The `app` declaration has the following fields:
- `wasp: dict` Required!
Wasp compiler configuration. It is a dictionary with a single field:
- `version: string` Required!
The version specifies which versions of Wasp are compatible with the app. It should contain a valid [SemVer range](https://github.com/npm/node-semver#ranges)
:::info
For now, the version field only supports caret ranges (i.e., `^x.y.z`). Support for the full specification will come in a future version of Wasp
:::
- `title: string` Required!
Title of your app. It will appear in the browser tab, next to the favicon.
- `head: [string]`
List of additional lines (e.g. `` or `