Skip to main content
Version: 0.15.0

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.

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, and consulting the 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:

main.wasp
// ...

query getAllTasks {
fn: import { getAllTasks } from "@src/queries.js"
}

query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries.js"
}

If you want to know about all supported options for the query declaration, take a look at the 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.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.ts, so that's where we should export them from.

Here's how you might implement the previously declared Queries getAllTasks and getFilteredTasks:

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)
}
Payload constraints

Wasp uses superjson under the hood. This means you're not limited to only sending and receiving JSON payloads.

You can send and receive any superjson-compatible payload (like Dates, Sets, Lists, circular references, etc.) and let Wasp handle the (de)serialization.

For a detailed explanation of the Query definition API (more precisely, its arguments and return values), check the 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.

import { getAllTasks, getFilteredTasks } from 'wasp/client/operations'

// ...

const allTasks = await getAllTasks()
const doneTasks = await getFilteredTasks({ isDone: true })

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 to authenticated Queries.
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 })

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. 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:

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 <div>There was an error</div>
}

return (
<div>
<h2>All Tasks</h2>
{allTasks && allTasks.length > 0
? allTasks.map((task) => <Task key={task.id} {...task} />)
: 'No tasks'}

<h2>Finished Tasks</h2>
{doneTasks && doneTasks.length > 0
? doneTasks.map((task) => <Task key={task.id} {...task} />)
: 'No finished tasks'}
</div>
)
}

const Task = ({ description, isDone }: Task) => {
return (
<div>
<p>
<strong>Description: </strong>
{description}
</p>
<p>
<strong>Is done: </strong>
{isDone ? 'Yes' : 'No'}
</p>
</div>
)
}

export default MainPage

For a detailed specification of the useQuery hook, check the 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:

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
)
}

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. To use an Entity in your Query, add it to the query declaration in Wasp:

main.wasp

query getAllTasks {
fn: import { getAllTasks } from "@src/queries.js",
entities: [Task]
}

query getFilteredTasks {
fn: import { getFilteredTasks } from "@src/queries.js",
entities: [Task]
}

Wasp will inject the specified Entity into the Query's context argument, giving you access to the Entity's Prisma API:

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 },
})
}

The object context.entities.Task exposes prisma.task from Prisma's CRUD API.

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.

Example

Declaring the Query:

query getFoo {
fn: import { getFoo } from "@src/queries.js"
entities: [Foo]
}

Enables you to import and use it anywhere in your code (on the server or the client):

// Use it on the client
import { getFoo } from 'wasp/client/operations'

// Use it on the server
import { getFoo } from 'wasp/server/operations'

On the the client, the Query expects

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 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 to see how to use the entities field on the context object, or the auth section to see how to use the user object.

Example

The following Query:

query getFoo {
fn: import { getFoo } from "@src/queries.js"
entities: [Foo]
}

Expects to find a named export getFoo from the file src/queries.js

queries.js
export const getFoo = (args, context) => {
// implementation
}

The useQuery Hook

Wasp's useQuery hook is a thin wrapper around the useQuery hook from 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 for this particular Query. If you want to change the global defaults, you can do so in the client setup function.

For an example of usage, check this section.