Basic Elements
App
There can be only one App
element per Wasp project. It serves as a starting point and defines global
properties of your app. Currently, it is very simple:
app: identifier
Name of your app.
title: string
Title of your app. It will be displayed in the browser tab, next to the favicon.
Page
Page
is the top-level layout abstraction. Your app can have multiple pages, and they are defined in Wasp
as follows:
page: identifier
Name of the page.
component: js import statement
Import statement of the page React element. See importing external code for details.
Page
also has to be associated with a Route
, otherwise it won't be accessible in the app.
authRequired: bool
Optional property - can be specified only if auth
is declared. If set to true
, only authenticated users will be able to access this page. Unauthenticated users will be redirected to a route declared by onAuthFailedRedirectTo
property within auth
.
If authRequired
is set to true
, the React component of a page (specified by component
property) will be provided user
object as a prop.
Check out this section of our Todo app tutorial for an example of usage.
Route
Using Route
element is a way to implement routing functionality in Wasp:
route: string
URL path of the route. Route string can be parametrised and follows the same conventions as React Router.
page: page identifier
Page identifier of the route's target. Referenced page must be defined somewhere in .wasp
file.
Example - parametrised URL path
For details on URL path format check React Router documentation.
Accessing route parameters in a page component
Since Wasp under the hood generates code with React Router, the same rules apply when accessing URL params in your React components. Here is an example just to get you started:
Navigating between routes
Navigation can be performed from the React code via <Link/>
component, also using the functionality of
React Router:
Entity
Entity
element represents a database model. Wasp uses Prisma to implement
database functionality and currently provides only a thin layer above it.
Each Entity
element corresponds 1-to-1 to Prisma data model and is defined in a following way:
entity: identifier
Name of the entity.
{=psl ... psl=}: PSL
Definition of entity fields in Prisma Schema Language (PSL). See here for intro and examples and here for a more exhaustive language specification.
Using entities
Entity-system in Wasp is based on Prisma, and currently Wasp provides only a thin layer on top of it. The workflow is as follows:
- Wasp developer creates/updates some of the entities in
.wasp
file. - Wasp developer runs
wasp db migrate-save <migration_name>
. - Migration data is generated in
migrations/
folder (and should be commited). - Wasp developer uses Prisma JS API to work with the database when in Operations.
Currently entities can be accessed only in Operations (Queries & Actions), so check their part of docs for more info on how to use entities in their context.
Queries and Actions (aka Operations)
In Wasp, main interaction between client and server happens via Operations, of which two types exist: Queries and Actions.
Query
Queries are NodeJS functions that don't modify any state. Normally they fetch certain resources, process them and return result. They are executed on server.
To create a Wasp Query, we need two things: declaration in Wasp and implementation in NodeJS:
NodeJS function above has to be async and will be passed query arguments as first argument and additional context as second argument.
By declaring a NodeJS function as a Wasp query, following happens:
- Wasp generates HTTP API route on the NodeJS server that calls the NodeJS query function.
- Wasp generates JS function on the client that has the name under which query was declared and takes same arguments as the NodeJS query function. Internally it uses above mentioned HTTP API route to call the NodeJS query function.
On client, you can import generated query JS function as import getTasks from '@wasp/queries/getTasks.js'
.
Then, you can either use it directly, or you can use it via special useQuery
React hook (provided by Wasp**) to make it reactive.
On server, you can import it the same way as on client, and then you can call it directly.
NOTE: Wasp will not stop you from importing NodeJS function directly on server, e.g. import { getAllTasks } from "./foo.js"
, but you shouldn't do it, because it will import pure NodeJS function and not a query recognized by Wasp, so it will not get all the features of a Wasp query.
useQuery
useQuery
hook provided by Wasp is actually just a thin wrapper for useQuery
hook from react-query.
You can import it as import { useQuery } from '@wasp/queries'
.
Wasp useQuery
takes three args:
queryFn
: client query function generated by Wasp based on query declaration, e.g. one you get by importing in JS like this:import getTasks from '@wasp/queries/getTasks.js'
.queryFnArgs
config
: react-queryconfig
.
It behaves exactly the same as useQuery from react-query, only it doesn't take the key, that is handled automatically instead.
Example of usage:
Error handling
For security reasons, all errors thrown in the query NodeJS function are sent to the client via HTTP API as 500 errors, with any further details removed, so that any unpredicted errors don't make it out with possibly sensitive data.
If you do want to throw an error that will pass some information to the client, you can use HttpError
in your NodeJS query function:
and then in client it will be thrown as an Error with corresponding .message
and .data
fields (if status code is 4xx - otherwise message
and data
will not be forwarded to the client, for security reasons).
This ensures that no error will accidentally leak out from the server, potentionally exposing sensitive data or implementation details.
Using entities
Most often, resources used by Operations will be Entities.
To use an Entity in your Operation, declare in Wasp that Operation uses it:
This will inject specified entity into the context of your Operation. Now, you can access Prisma API for that entity like this:
where context.entities.Task
actually exposes prisma.task
from Prisma API.
Cache invalidation
One of the trickiest part of managing web app state is making sure that data which queries are showing is up to date.
Since Wasp is using react-query for managing queries, that means we want to make sure that parts of react-query cache are invalidated when we know they are not up to date any more.
This can be done manually, by using mechanisms provided by react-query (refetch, direct invalidation). However, that can often be tricky and error-prone, so Wasp offers quick and effective solution to get you started: automatic invalidation of query cache based on entities that queries / actions are using.
Specifically, if Action A1 uses Entity E1 and Query Q1 also uses Entity E1 and Action A1 is executed, Wasp will recognize that Q1 might not be up-to-date any more and will therefore invalidate its cache, making sure it gets updated.
In practice, this means that without really even thinking about it, Wasp will make sure to keep the queries up to date for you in regard with the changes done by actions.
On the other hand, this kind of automatic invalidation of cache can be wasteful (updating when not needed) and will not work if other resources than entities are used. In that case, make sure to use mechanisms provided by react-query for now, and expect more direct support in Wasp for handling those use cases in a nice, elegant way.
Action
Actions are very similar to Queries, so similar that we will only list the differences:
- They can modify state (queries can't).
- There is no special React hook for them (like
useQuery
for Queries), you just call them directly. - They are declared in Wasp in same way as Queries, but keyword is
action
, notquery
.
More differences and action/query specific features will come in the future versions of Wasp.
Dependencies
You can specify additional npm dependencies in following way, in your *.wasp
file:
You will need to re-run wasp start
after adding a dependency for Wasp to pick it up.
NOTE: In current implementation of Wasp, if Wasp is already internally using certain npm dependency with certain version specified, you are not allowed to define that same npm dependency yourself while specifying different version. If you do that, you will get an error message telling you which exact version you have to use for that dependency. This means Wasp dictates exact versions of certain packages, so for example you can't choose version of React you want to use. In the future, we will add support for picking any version you like, but we have not implemented that yet. Check issue #59 to check out the progress or contribute.
Authentication & Authorization
Wasp provides authentication and authorization support out-of-the-box. Enabling it for your app is optional and can be done by adding auth
element to your .wasp
file:
userEntity: entity
Entity which represents the user (sometimes also referred to as Principal).
methods: [AuthMethod]
List of authentication methods that Wasp app supports. Currently supported methods are:
EmailAndPassword
: Provides support for authentication with email address and a password.
onAuthFailedRedirectTo: String
Name of the route where an unauthenticated user will be redirected to if they try to access a private page (which is declared by setting authRequired: true
for a specific page).
Check out this section of our Todo app tutorial to see an example of usage.
Email and Password
EmailAndPassword
authentication method makes it possible to signup/login into the app by using email address and a password.
This method requires that userEntity
specified in auth
element contains email: string
and password: string
fields.
High-level API
The quickest way to get started is by using the following API generated by Wasp:
- Signup and Login forms at
@wasp/auth/forms/Signup
and@wasp/auth/forms/Login
routes logout
functionuseAuth()
React hook
Check our Todo app tutorial to see how it works. See below for detailed specification of each of these methods.
Lower-level API
If you require more control in your authentication flow, you can achieve that in the following ways:
- If you don't want to use already generated Signup and Login forms and want to create your own, you can use
signup
andlogin
function by invoking them from the client. - If you want to execute custom code on the server during sign up, create your own sign up action which invokes Prisma client as
context.entities.[USER_ENTITY].create()
function, along with your custom code.
The code of your custom sign-up action would look like this (your user entity being User
in this instance):
info
You don't need to worry about hashing the password yourself! Even when you are using Prisma's client directly and calling create()
with a plain-text password, Wasp put middleware in place that takes care of hashing it before storing it to the database.
Specification
login()
An action for logging in the user.
email: string
Email of the user logging in.
password: string
Password of the user logging in.
import statement
:
Login is a regular action and can be used directly from the frontend.
signup()
An action for signing in in the user.
userFields: object
Fields of user entity which was declared in auth
.
import statement
:
Signup is a regular action and can be used directly from the frontend.
logout()
An action for logging out the user.
import statement
:
Example of usage:
Reset password
Coming soon.
Updating user's password
If you need to update user's password, you can do it safely via Prisma client, e.g. within an action:
You don't need to worry about hashing the password yourself - if you have an auth
declaration
in your .wasp
file, Wasp already set a middleware on Prisma that makes sure whenever password
is created or updated on the user entity, it is also hashed before it is stored to the database.
Accessing currently logged in user
When authentication is enabled in a Wasp app, we need a way to tell whether a user is logged in and access its data. With that, we can further implement access control and decide which content is private and which public.
On client
On client, Wasp provides useAuth
React hook to be used within the functional components.
useAuth
is actually a thin wrapper over Wasp's useQuery
hook and returns data in the exactly same
format.
useAuth()
import statement
:
Example of usage:
On server
When authentication is enabled, all the operations (actions and queries) will have user
object
present in the context
argument. context.user
will contain all the fields from the user entity
except for the password.
Example of usage:
In order to implement access control, each operation is responsible for checking context.user
and
acting accordingly - e.g. if context.user
is undefined
and the operation is private then user
should be denied access to it.