diff --git a/web/docs/advanced/apis.md b/web/docs/advanced/apis.md index 0399f9a8d7..be42ecc083 100644 --- a/web/docs/advanced/apis.md +++ b/web/docs/advanced/apis.md @@ -1,3 +1,224 @@ --- title: Custom HTTP API Endpoints --- +import { ShowForTs, ShowForJs } from '@site/src/components/TsJsHelpers'; + +In Wasp, the default client-server interaction mechanism is through [Operations](/docs/database/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. + +## How to create an API + +APIs are used to tie a JS function to an 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: +1. Declare the API in Wasp using the `api` declaration +2. Define the API's NodeJS implementation + +After completing these two steps, you'll be able to call the API from the client code (via our `Axios` wrapper), or from the outside world. + +### Declaring the API in Wasp +First we need to declare the API in the Wasp file and you can easily do this with the `api` declaration: + +```wasp title="main.wasp" +// ... + +api fooBar { // APIs and their implementations don't need to (but can) have the same name. + fn: import { fooBar } from "@server/apis.js", + httpRoute: (GET, "/foo/bar") +} +``` + +The `api` declaration supports the following fields: +- `fn: ServerImport` (required) - The import statement of the APIs NodeJs implementation. +- `httpRoute: (HttpMethod, string)` (required) - The HTTP (method, path) pair, where the method can be one of: + - `ALL`, `GET`, `POST`, `PUT` or `DELETE` + - and path is an Express path `string`. +- `entities: [Entity]` (optional) - A list of entities you wish to use inside your API. +We'll leave this option aside for now. You can read more about it [here](#using-entities-in-apis). +- `auth: bool` (optional) - If auth is enabled, this will default to `true` and provide a `context.user` object. If you do not wish to attempt to parse the JWT in the Authorization Header, you may set this to `false`. +- `middlewareConfigFn: ServerImport` (optional) - The import statement to an Express middleware config function for this API. See [the guide here](/docs/advanced/middleware-config). + +### Defining the APIs NodeJS implementation + + + +:::note +To make sure the Wasp compiler generates the types for APIs for use in the NodeJS implementation, you should add your `api` declarations to your `.wasp` file first _and_ keep the `wasp start` command running. +::: + + +After you defined the API, it should be implemented as a NodeJS function that takes three arguments: +1. `req`: Express Request object +2. `res`: Express Response object +3. `context`: 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](#using-entities-in-apis). + + + + +```ts title="src/server/apis.js" +export const 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?.username || "stranger"}!` }) +} +``` + + + +```ts title="src/server/apis.ts" +import { FooBar } from '@wasp/apis/types' // This type is generated by Wasp based on the `api` declaration above. + +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?.username || "stranger"}!` }) +} +``` + + + + + +#### Providing extra type information + +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: + +```wasp title="main.wasp" +api fooBar { + fn: import { fooBar } from "@server/apis.js", + entities: [Task], + httpRoute: (GET, "/foo/bar/:email") +} +``` + +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. + +```ts title="src/server/apis.ts" +import { FooBar } from '@wasp/apis/types' + +export const fooBar: FooBar< +{ email: string }, // params +{ answer: number } // response +> = (req, res, _context) => { + console.log(req.params.email) + res.json({ answer: 42 }) +} +``` + + +## Using the API + +### Using the API externally + +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.). + +### Using the API from the client + +To use the API from your client, including with auth support, you can import the Axios wrapper from `@wasp/api` and invoke a call. For example: +```ts +import React, { useEffect } from 'react' +import api from '@wasp/api' + +async function fetchCustomRoute() { + const res = await api.get('/foo/bar') + console.log(res.data) +} + +export const Foo = () => { + useEffect(() => { + fetchCustomRoute() + }, []); + + return ( + <> + // ... + + ) +} +``` + +#### Making sure CORS works +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 declaration used to apply some `middlewareConfigFn` to all APIs under some specific path: +```wasp title="main.wasp" +apiNamespace fooBar { + middlewareConfigFn: import { fooBarNamespaceMiddlewareFn } from "@server/apis.js", + path: "/foo" +} +``` +And then in the implementation file: +```js title="src/server/apis.js" +export const apiMiddleware = (config) => { + return config; +} +``` + + + +For example, an `apiNamespace` is a simple declaration used to apply some `middlewareConfigFn` to all APIs under some specific path: +```wasp title="main.wasp" +apiNamespace fooBar { + middlewareConfigFn: import { fooBarNamespaceMiddlewareFn } from "@server/apis.js", + path: "/foo" +} +``` +And then in the implementation file (returning the default config): +```ts title="src/server/apis.ts" +import { MiddlewareConfigFn } from "@wasp/middleware" +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](/docs/advanced/middleware-config) + + +## Using Entities in APIs +In many cases, resources used in APIs will be [Entities](#entity). +To use an Entity in your API, add it to the `api` declaration in Wasp: + +```wasp {3} title="main.wasp" +api fooBar { + fn: import { fooBar } from "@server/apis.js", + entities: [Task], + httpRoute: (GET, "/foo/bar") +} +``` + +Wasp will inject the specified Entity into the APIs `context` argument, giving you access to the Entity's Prisma API: + + + + +```ts title="src/server/apis.js" +export const fooBar = (req, res, context) => { + res.json({ count: await context.entities.Task.count() }) +} +``` + + + +```ts title="src/server/apis.ts" +import { FooBar } from '@wasp/apis/types' + +export const fooBar: FooBar = (req, res, context) => { + res.json({ count: await context.entities.Task.count() }) +} +``` + + + +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).