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-dev
. - 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.
Server configuration
With server
declaration, you can configure behaviour of the NodeJS server (one that is executing the Operations).
Currently server
supports only one option, setupFn
, but there will likely be more options added in the future.
setupFn
setupFn
declares a JS function that will be executed on server start. This function is expected to be async and will be awaited before server continues with its setup and starts serving any requests.
It gives you an opportunity to do any custom setup, e.g. setting up additional database or starting cron/scheduled jobs.
The javascript function should be async, takes no arguments and its return value is ignored.
In case you want to store some values for later use, or to be accessed by the Operations, recommended way is to store those in variables in the same module/file where you defined the javascript setup function and then expose additional functions for reading those values, which you can then import directly from Operations and use. This effectively turns your module into a singleton whose construction is performed on server start.
Dummy example of such function and its usage:
.env
Your project will likely be using environment variables for configuration, typically to define connection to the database, API keys for external services and similar.
When in production, you will typically define environment variables through mechanisms provided by your hosting provider.
However, when in development, you might also need to supply certain environment variables, and to avoid doing it "manually", Wasp supports .env
(dotenv) file where you can define environment variables that will be used during development (they will not be used during production).
.env
file has to be defined in the root of your Wasp project.
.env
file should not be commited to the version control - we already ignore it by default in the .gitignore file we generate when you create a new Wasp project via wasp new
cli command.
Variables are defined in .env
in the form of NAME=VALUE
, for example:
Any env vars defined in the .env
will be forwarded to the server-side of your Wasp project and therefore accessible in your nodejs code via process.env
, for example:
Database
You can specify database to be used by Wasp via db
element (there can be only one such element per Wasp project):
If you don't have db
block, default database is used (which is SQLite
).
If you create or modify db
declaration, run wasp db migrate-dev
to apply the changes.
system: identifier
Database system that Wasp will use. It can be either PostgreSQL
or SQLite
.
SQLite
Default database is SQLite
, since it is great for getting started with a new project (needs no configuring), but it can be used only in development - once you want to deploy Wasp to production you will need to switch to PostgreSQL
and stick with it.
Check below for more details on how to migrate from SQLite to PostgreSQL.
PostgreSQL
When using PostgreSQL
(db { system: PostgreSQL }
), you will need to spin up a postgres database on your own so it runs during development (when running wasp start
or doing wasp db ...
commands) and provide Wasp with DATABASE_URL
environment variable that Wasp will use to connect to it.
One of the easiest ways to do this is by spinning up postgres docker container when you need it with the shell command
and adding the line
to the .env
file in the root directory of your Wasp project.
Migrating from SQLite to PostgreSQL
To run Wasp app in production, you will need to switch from SQLite
to PostgreSQL
.
- Set
db.system
toPostgreSQL
and setDATABASE_URL
env var accordingly (as described above). - Delete old migrations, since they are SQLite migrations and can't be used with PostgreSQL:
rm -r migrations/
. - Run
wasp db migrate-dev
to apply new changes and create new, initial migration. You will need to have your postgres database running while doing this (check above for easy way to get it running).