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 {}, });