Navigated to /docs/endpoints/writing-endpoints.

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:

server/endpoints/todos/[todoId].js
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:

server/endpoints/admin.auth.js
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.

server/endpoints/todos.js
export async function post() {
  // handle POST request
}

export async function get() {
  // handle GET request
}
Caution
Since delete is a reserved word in JavaScript, use del() to handle DELETE requests.

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.

server/endpoints/todos.js
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.

server/endpoints/hello-world.js
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.

server/endpoints/todos/[todoId].js
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.

server/endpoints/hello-world.js
export default async function () {
  return new Response('Hello!');
}

You can also return the result of a fetch directly, since fetch always returns a Response:

server/endpoints/todos.js
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:

server/endpoints/hello-world.js
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:

server/endpoints/webhook.js
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:

server/endpoints/hello-world.js
import { redirect } from '@nokkio/endpoints';

export default async function () {
  return redirect('/');
}

To redirect to another endpoint, use redirectToEndpoint:

server/endpoints/step-1.js
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:

server/endpoints/todo/[todoId].js
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:

server/endpoints/github.js
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:

server/endpoints/write-file.js
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.

server/endpoints/write-file.js
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.

server/endpoints/write-image.js
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 });
}
Caution
If your application uploads images directly from the frontend, use @nokkio/forms to manage the upload. Only use writeImage directly if you are retrieving the image from a third party (e.g. an API).

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.

Caution
The boot function must complete before your endpoint can respond, be careful how much latency you introduce in the boot() function by only performing what is absolutely necessary.