web/docs/advanced/apis.md
import { CardLink } from '@site/src/components/CardLink' import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers' import ReferencingCodeFromSrcNote from '../_referencing-code-from-src-note.md'
In Wasp, the default client-server interaction mechanism is through Operations. However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an api. Best of all, they should look and feel very familiar.
APIs are used to tie a JS function to a certain endpoint e.g. POST /something/special. They are distinct from Operations and have no client-side helpers (like useQuery).
To create a Wasp API, you must:
api constructorAfter completing these two steps, you'll be able to call the API from the client code (via our ky wrapper), or from the outside world.
First, we need to declare the API in the Wasp file and you can easily do this with the api function:
import { api, app } from "@wasp.sh/spec"
import { fooBar } from "./src/apis" with { type: "ref" }
export default app({
// ...
spec: [
api("GET", "/foo/bar", fooBar),
],
})
Read more about the supported fields in the API Reference.
After you defined the API, it should be implemented as a NodeJS function that takes three arguments:
req: Express Request objectres: Express Response objectcontext: An additional context object injected into the API by Wasp. This object contains user session information, as well as information about entities. The examples here won't use the context for simplicity purposes. You can read more about it in the section about using entities in APIs.import type { FooBar } from "wasp/server/api";
export const fooBar: FooBar = (req, res, context) => {
res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware.
res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` });
};
We'll see how we can provide extra type information to an API function.
Let's say you wanted to create some GET route that would take an email address as a param, and provide them the answer to "Life, the Universe and Everything." 😀 What would this look like in TypeScript?
Define the API in Wasp:
import { api, app } from "@wasp.sh/spec"
import { fooBar } from "./src/apis" with { type: "ref" }
export default app({
// ...
spec: [
api("GET", "/foo/bar/:email", fooBar, { entities: ["Task"] }),
],
})
We can use the FooBar type to which we'll provide the generic params and response types, which then gives us full type safety in the implementation.
import { FooBar } from "wasp/server/api";
export const fooBar: FooBar<
{ email: string }, // params
{ answer: number } // response
> = (req, res, _context) => {
console.log(req.params.email);
res.json({ answer: 42 });
};
To use the API externally, you simply call the endpoint using the method and path you used.
For example, if your app is running at https://example.com then from the above you could issue a GET to https://example/com/foo/callback (in your browser, Postman, curl, another web service, etc.).
To use the API from your client, including with auth support, you can import the api instance from wasp/client/api. It is a ky instance pre-configured with the API base URL, authentication, and error handling. For example:
import React, { useEffect } from "react";
import { api } from "wasp/client/api";
async function fetchCustomRoute() {
const data = await api.get("/foo/bar").json();
console.log(data);
}
export const Foo = () => {
useEffect(() => {
fetchCustomRoute();
}, []);
return <>{$HOLE$}</>;
};
APIs are designed to be as flexible as possible, hence they don't utilize the default middleware like Operations do. As a result, to use these APIs on the client side, you must ensure that CORS (Cross-Origin Resource Sharing) is enabled.
You can do this by defining custom middleware for your APIs in the Wasp file.
For example, an apiNamespace is a simple spec used to apply some middlewareConfigFn to all APIs under some specific path:
import { apiNamespace, app } from "@wasp.sh/spec"
import { apiMiddleware } from "./src/apis" with { type: "ref" }
export default app({
// ...
spec: [
apiNamespace("/foo", { middlewareConfigFn: apiMiddleware }),
],
})
And then in the implementation file (returning the default config):
import type { MiddlewareConfigFn } from "wasp/server";
export const apiMiddleware: MiddlewareConfigFn = (config) => {
return config;
};
We are returning the default middleware which enables CORS for all APIs under the /foo path.
For more information about middleware configuration, please see: Middleware Configuration
In many cases, resources used in APIs will be Entities.
To use an Entity in your API, add it to the api spec in Wasp:
import { api, app } from "@wasp.sh/spec"
import { fooBar } from "./src/apis" with { type: "ref" }
export default app({
// ...
spec: [
api("GET", "/foo/bar", fooBar, { entities: ["Task"] }),
],
})
Wasp will inject the specified Entity into the APIs context argument, giving you access to the Entity's Prisma API:
import type { FooBar } from "wasp/server/api";
export const fooBar: FooBar = async (req, res, context) => {
res.json({ count: await context.entities.Task.count() });
};
The object context.entities.Task exposes prisma.task from Prisma's CRUD API.
You can use streaming responses to send data to the client in chunks as it becomes available. This is useful for:
To create a streaming API, write a function that uses Express response methods like res.write() and res.end():
import { api, app } from "@wasp.sh/spec"
import { getStreamingText } from "./src/streaming" with { type: "ref" }
export default app({
// ...
spec: [
api("POST", "/api/streaming-example", getStreamingText),
],
})
import OpenAI from "openai";
import type { GetStreamingText } from "wasp/server/api";
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export const getStreamingText: GetStreamingText<
never,
string,
{ message: string }
> = async (req, res) => {
const { message } = req.body;
// Set appropriate headers for streaming.
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setHeader("Transfer-Encoding", "chunked");
const stream = await client.responses.create({
model: "gpt-5",
input: `Funny response to "${message}"`,
stream: true,
});
for await (const chunk of stream) {
if (chunk.type === "response.output_text.delta") {
// Write each chunk to the response as it arrives.
res.write(chunk.delta);
}
}
// End the response.
res.end();
};
You can consume streaming responses on the client using the api instance from wasp/client/api. Since ky is built on fetch, you get native streaming support via the Response.body readable stream. The api instance handles authentication automatically.
import { useEffect, useState } from "react";
import { api } from "wasp/client/api";
export function StreamingPage() {
const { response } = useTextStream("/api/streaming-example", {
message: "Best Office episode?",
});
return (
<div>
<h1>Streaming Example</h1>
<pre>{response}</pre>
</div>
);
}
function useTextStream(path: string, payload: { message: string }) {
const [response, setResponse] = useState("");
useEffect(() => {
const controller = new AbortController();
fetchStream(
path,
payload,
(chunk) => {
setResponse((prev) => prev + chunk);
},
controller.signal
);
return () => {
controller.abort();
};
}, [path]);
return { response };
}
async function fetchStream(
path: string,
payload: { message: string },
onData: (data: string) => void,
signal: AbortSignal
) {
try {
const response = await api.post(path, {
json: payload,
signal,
});
if (response.body === null) {
throw new Error("Stream body is null");
}
const stream = response.body.pipeThrough(new TextDecoderStream());
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
onData(value);
}
} catch (error: unknown) {
if (error instanceof Error) {
if (error.name === "AbortError") {
// Fetch was aborted, no need to log an error
return;
}
console.error("Fetch error:", error.message);
} else {
throw error;
}
}
}
<CardLink to="../api/@wasp.sh/spec/functions/api" kind="api" title="api" description="All the options for declaring an API endpoint in the Wasp spec." />
<CardLink to="../api/@wasp.sh/spec/functions/apiNamespace" kind="api" title="apiNamespace" description="All the options for declaring an API namespace in the Wasp spec." />