Handle Requests

Handle is the core method of this project. It wraps your getServerSideProps handler so that you can handle post requests in addition to get requests, and so that you'll have an automatically generated JSON api as well.

#
Handle GET requests

Define the get handler to support GET requests, the standard getServerSideProps functionality that you're already familiar with. The return value from this handler is passed as props to the page component when the user visits the page.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async get(context) {
    return json({
      name: 'Stephan Meijer',
      country: 'Netherlands',
    });
  },
});

The context parameter is the object as provided by Next.js GetServerSidePropsContext extended with cookie and header handlers as well as query expansion.

#
Query expansion

When using next-runtime, URL Search Parameters are expanded. Take the following url:

https://example.com?person.name=smeijer&person.age=34

Without using next-runtime, context.query would look like:

context.query = {
  'person.name': 'smeijer',
  'person.age': '34',
};

With query expansion that becomes:

context.query = {
  person: {
    name: 'smeijer',
    age: '34',
  },
};

This works with arrays as well (person[n].name).

#
Get JSON API

Users don't always visit a web page to consume their information. Sometimes, they want to use a shell script, and request data in JSON format. Every next-runtime handler also serves as JSON endpoint. Even the get handler. Request the same page via curl, and you'll be served JSON instead.

curl -H "Accept: application/json" https://example.com/page-path
# » { name: 'Stephan Meijer', country: 'Netherlands' }

#
Handle POST Requests

Define the post handler to support POST requests. Post requests can be used to submit forms to the current page, without the need to create an api-route.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async post(context) {
    return json({
      name: 'Stephan Meijer',
      country: 'Netherlands',
    });
  },
});

The context parameter is the object as provided by Next.js GetServerSidePropsContext extended with cookie and header handlers, and a populated req.body.

#
Post Request Body

The request body contains an object, holding the contents that were submitted by the form or json post request.

For example, take the following form:

<form action="post">
  <input name="name" />
  <input country="country" />
</form>

When the user would submit that form with the values Person and Some Place, req.body would equal:

req.body = {
  name: 'Person',
  country: 'Some Place',
};

Note that query expansion is applied to the request body as well. A submitted field name like persons[0].name translates to a req.body like { persons: [{ name: '' }].

#
Post JSON API

Submitting a form is one way to receive post data, but every post handler also automatically serves as an api route. Users might just as well open curl and post data via the terminal:

curl \
  -H "Content-Type: application/json" \
  -d '{ "name": "Stephan", "country": "Netherlands" }' \
  https://example.com/page-path

#
Handle PATCH Requests

Define the patch handler to support PATCH requests. They behave the same as POST requests, so please check there for more specifics.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async patch() {},
});

#
Handle PUT Requests

Define the put handler to support PUT requests. They behave the same as POST requests, so please check there for more specifics.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async put() {},
});

#
Handle DELETE Requests

Define the delete handler to support DELETE requests. They behave the same as POST requests, so please check there for more specifics.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async delete() {},
});

#
Handle File Uploads

To allow POST, PATCH and PUT requests to include files, you'll need to set the uploadDir or define the upload handler. Note that this a supporting handler that accompanies the method handlers. You can have a post without an upload handler, but an upload without a post makes no sense. When neither of the options are provided, file uploads will be ignored.

The examples below are based on the following form:

<form action="post" enctype="multipart/form-data">
  <input name="file" type="file" />
</form>

#
uploadDir

The easiest way to support file uploads, is by setting the uploadDir option. When providing this option, files will be written to the local file system in the directory of your choice. Files will be stored with randomly generated filenames, and the full path will be available as file.path in the request body.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  uploadDir: '/uploads',

  async post({ req: { body } }) {
    return json({
      message: `Your file is stored at ${body.file.path}.`,
    });
  },
});

#
upload handler

A more advanced method to handle file uploads, is by defining the upload handler. The upload handler receives an object as argument, holding three properties:

  • field string

    The form field name under which the file was selected.

  • file { name: string, size: number, type: string }

    The file descriptor providing data about the uploaded file.

  • stream ReadableStream

    The stream to be piped to stream writers.

Use the readable stream to pipe the file upload directly to an object store like Amazon S3 or Digital Ocean spaces.

import { handle, json } from 'next-runtime';

export const getServerSideProps = handle({
  async upload({ field, file, stream }) {
    stream.pipe(fs.createWriteStream(`/uploads/${file.name}`));
  },

  async post({ req: { body } }) {
    return json({
      message: `Your file is stored at ${body.file.path}.`,
    });
  },
});

#
Upload API

Uploads can be done via the api route as well. For that, it depends on the post handler. Give it a shot with the following CURL command:

curl --form file='@photo.png' https://example.com/page-path

#
Limits

We use a combination of body-parser and busboy to parse the incoming data, together with our own glue for query expansion.

To protect your apis from being misused, it's possible to add restrictions using the limits option. All limits are optional.

import { handle } from 'next-runtime';

export const getServerSideProps = handle({
  limits: {},
});

The following options are available.

  • fileCount number

    The maximum number of files a user can upload.

  • fileSize number | string

    The maximum size per file in bytes. ¹⁾

  • fieldSize number | string

    The maximum size per field in bytes. ¹⁾

  • jsonSize number | string

    The maximum size of the full json payload. ¹⁾

  • mimeType string

    A comma-separated list of one or more file types or unique file type specifiers to only accept files of certain types. See MDN for more info. It's the same accept string as <input type="file" accept="..." /> uses.

#
TypeScript

handle accepts three type parameters. The first two are what you're familiar with from Next.js, PageProps and UrlQuery. PageProps is used to shape the get and post return value, while UrlQuery is used to shape context.params. The last one, FormData is one that we added and applies to req.body.

Our RuntimeContext extends GetServerSidepropsContext with convenient cookie and header helpers.

export const getServerSideProps = handle<PageProps, UrlQuery, FormData>({
  async upload({ file: File, stream: ReadableStream }): void {},
  async get({ params: UrlQuery }: RuntimeContext): RuntimeResponse {},
  async post({ req: { body: FormData } }: RuntimeContext): RuntimeResponse {},
  async put({ req: { body: FormData } }: RuntimeContext): RuntimeResponse {},
  async patch({ req: { body: FormData } }: RuntimeContext): RuntimeResponse {},
  async delete({ req: { body: FormData } }: RuntimeContext): RuntimeResponse {},
});