Writing endpoints
All you need to know about writing Nokkio endpoints
Endpoints are defined in server/endpoints and adhere to a contract built around the Fetch web APIs. The handler function receives a Request, and is only required to return a Response. What happens is between is up to you!
function handler(request: Request): Promise<Response>;
Routing
Endpoints use the same file-based routing your Nokkio application uses for the pages directory, including parameterized routes and routes that are only accessible for authenticated users.
server/endpoints ├── todos │ ├── index.js │ └── [id].js ├── index.js └── secrets.auth.js
Parameterized routes
When handling a parameterized route, the value of the parameter can be found in the request argument that is passed to your handler function:
export default async function (request) {
// Example: /_/endpoints/todo/todo_123
// todoId will equal ‘todo_123’
const todoId = request.params.todoId;
}
Authorized routes
Endpoints also support the .auth.js suffix (or .auth.ts if you are using TypeScript) to make a endpoint available to only logged-in users. Users who are not authenticated will receive a 401 Unauthorized response and your handler function will not be executed.
The current user ID can be found in the request argument passed to your handler function:
export default async function (request) {
const userId = request.userId;
}
HTTP methods
When an endpoint exports a default handler, that handler will be called for any request to the endpoint, regardless of the HTTP method used. In practice, most endpoints will only want to respond to a particular HTTP method, or have different behavior for different methods.
Nokkio Endpoints supports per-method behavior by using named exports in place of a default export. The following endpoint only responds to GET and POST requests, any other method would return a 404 Not Found.
export async function post() {
// handle POST request
}
export async function get() {
// handle GET request
}
You can also combine named exports and a default export. The default export will catch any requests that do not have a matching handler for the request’s HTTP method.
export async function post() {
// handle POST request
}
export default async function () {
// handle all requests that are not a POST
}
The Request object
Endpoint handlers are passed a Request object extended with two additional pieces of information: the Nokkio userId and, for parameterized routes, the values of matched parameters found in the request's URL.
request.userId
For projects that use Nokkio’s built-in authentication system, the request object is decorated with a userId property that contains the id of the currently authenticated user. If the user is not authenticated, userId will be null.
export default async function (request) {
const userId = request.userId;
}
request.params
When responding to a parameterized route, request.params is populated with the values of the resolved parameters in the request's URL.
export default async function (request) {
// Example: /_/endpoints/todo/todo_123
// todoId will equal ‘todo_123’
const todoId = request.params.todoId;
}
Responses
Endpoint handlers must return a Response object. For simple use cases, create a new Response directly. The following endpoint returns a plain-text response with "Hello!" as the body.
export default async function () {
return new Response('Hello!');
}
You can also return the result of a fetch directly, since fetch always returns a Response:
export default function () {
return fetch(
'https://jsonplaceholder.typicode.com/todos'
);
}
Creating a JSON response
A common pattern for endpoints is to return a JSON response to be consumed by your front-end application. The @nokkio/endpoints package provides a helper function that serializes the provided object into JSON and sets the content type of the response to application/json:
import { json } from '@nokkio/endpoints';
export default async function () {
return json({ success: true });
}
Returning an empty 200 response
Sometimes all an endpoint needs to do is acknowledge that it was successful by returning an empty 200 response. You can use the ok()method as a shortcut:
import { ok } from '@nokkio/endpoints';
export default async function () {
// process webhook
return ok();
}
Redirects
Use the redirect helper to forward the user to a page in your Nokkio application:
import { redirect } from '@nokkio/endpoints';
export default async function () {
return redirect('/');
}
To redirect to another endpoint, use redirectToEndpoint:
import { redirectToEndpoint } from '@nokkio/endpoints';
export default async function () {
// redirects to /_/endpoints/step-2
return redirectToEndpoint('/step-2');
}
Accessing data
@nokkio/magic models are available in your endpoints. The following endpoint loads a Todo based on the ID passed to the endpoint:
import { json } from '@nokkio/endpoints';
import { Todo } from '@nokkio/magic';
export default async function (request) {
const todoId = request.params.todoId;
const todo = await Todo.findById(listId);
return json({ todo });
}
Secrets
A common use case for endpoints involves accessing an API with secret keys you don't want to expose on the client. Nokkio offers built-in secret management, storing your secrets and securely providing them to your endpoints at runtime.
Creating a secret
Secrets are created with the CLI's nokkio secret create command. Note that each secret requires an environment of development orproduction and in most cases you will need a version of the secret for both, even if the value is the same.
nokkio secret create githubApiKey \ --environment development
For more on managing secrets with nokkio secret, see the CLI documentation.
Using secrets in your endpoints
To access the appropriate value for your secret, use the getSecret function from @nokkio/endpoints:
import { getSecret } from '@nokkio/endpoints';
export default async function () {
const githubToken = getSecret('githubApiKey');
// fetch using the GitHub API
}
Writing files
@nokkio/endpoints provides two functions for writing files in the endpoints runtime: the generic writeFile for writing any file, or writeImage for writing images destined to be used with @nokkio/image.
writeFile
To write a file, import the writeFile function from @nokkio/endpoints, then pass it a filename and a string, Blob, or ReadableStream:
import {
json,
writeFile,
getPublicFileUrl,
} from '@nokkio/endpoints';
export default async function () {
const { path } = await writeFile(
'test.txt',
'hello world!',
);
// path will be a unique, relative path to the file.
// you can store this in a Nokkio model for later use,
// then use getPublicFileUrl to generate a fully
// qualified URL at runtime.
return json({ url: getPublicFileUrl(path) });
}
Nokkio stores the file securely and privately in durable cloud storage. Only those with access to the output of getPublicFileUrl will have access to the content.
import {
json,
writeFile,
getPublicFileUrl,
} from '@nokkio/endpoints';
export default async function () {
const { path } = await writeFile(
'test.txt',
'hello world!',
);
// path will be a unique, relative path to the file.
// you can store this in a Nokkio model for later use,
// then use getPublicFileUrl to generate a fully
// qualified URL at runtime.
return json({ url: getPublicFileUrl(path) });
}
Nokkio stores the file securely and privately in durable cloud storage. Only those with access to the output of getPublicFileUrl will have access to the content.
writeImage
Similarly, use the writeImage function to store an image for use with @nokkio/image.
import {
json,
writeImage,
getPublicFileUrl,
} from '@nokkio/endpoints';
export default async function () {
const response = await fetch(
'https://example.com/image.jpg',
);
if (!response.body) {
return new Response('not found', { status: 404 });
}
// response.body is a ReadableStream, so it can
// be passed directly to writeImage
const { path, metadata } = await writeImage(
'test.jpg',
response.body,
);
// path will be a unique, relative path to the image.
// you can store this in a Nokkio model for later use,
// then use getPublicFileUrl to generate a fully
// qualified URL at runtime.
return json({ url: getPublicFileUrl(path), metadata });
}
boot()
If you define a function as the default export in the server/boot.jsfile (or server/boot.ts if you are using TypeScript), Nokkio will execute that function before handling requests for your endpoint. The boot() function is mostly used for defining gates and event handlers for your @nokkio/magic models, but can also be used for any other setup that your application needs to occur before handling endpoints.