Method
A method definition describes how an individual request to an endpoint should be handled, for a given HTTP method.
Configuration
A method definition can be configured to change its behaviour when executed.
Handler
The handler defines the main logic used when executing the method. It will be called with two objects, handlerData
and handlerApi
.
It's expected to execute some logic, and optionally return some data. Depending on what the method handler returns, the final endpoint handler will send a different status and response:
Handler result | Status | Final response |
---|---|---|
Data returned | 200 | Data sent via res.json() |
No data returned (undefined ) | 204 | res.end() called |
Error thrown | 500 | Serialised error sent via res.json() |
- TypeScript
- JavaScript
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
get: method({
// res.status(200).json('foo')
handler: () => 'foo',
}),
put: method({
// res.status(204).end();
handler: ({ body }) => {
console.log(body);
},
}),
post: method({
// res.status(500).json(serializeError(new Error('oops!')));
handler: () => {
throw new Error('oops!');
},
}),
}),
});
export default endpoint.handler;
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
get: method({
// res.status(200).json('foo')
handler: () => 'foo',
}),
put: method({
// res.status(204).end();
handler: ({ body }) => {
console.log(body);
},
}),
post: method({
// res.status(500).json(serializeError(new Error('oops!')));
handler: () => {
throw new Error('oops!');
},
}),
}),
});
export default endpoint.handler;
Custom codes
In cases where you want to use a different status code, you can use the failWithCode
and succeedWithCode
utilities provided as part of the handlerApi
object (second parameter for handler callback).
- TypeScript
- JavaScript
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
post: method<{ created: true }>({
handler: ({ body }, { failWithCode, succeedWithCode }) => {
console.log(body);
if (!body) {
throw failWithCode(400, 'No body provided');
}
return succeedWithCode(201, { created: true });
},
}),
}),
});
export default endpoint.handler;
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
post: method({
handler: ({ body }, { failWithCode, succeedWithCode }) => {
console.log(body);
if (!body) {
throw failWithCode(400, 'No body provided');
}
return succeedWithCode(201, { created: true });
},
}),
}),
});
export default endpoint.handler;
failWithCode
can be returned or thrown, but it's generally better to throw it yourself.
Disabling default response handling
Typically, the default behaviour is preferable as it leads to less code overall.
However, sometimes it might be desireable to use the res
parameter to send your response yourself (using it as a writeable stream, for example).
To indicate that you have already sent the response and don't want CEF to conduct its usual response handling, you can return the nothing
symbol exported from CEF inside your handler.
import { nothing } from 'next-create-endpoint-factory';
import { createEndpoint } from '../../src/api';
import stream from 'stream';
import { promisify } from 'util';
const pipeline = promisify(stream.pipeline);
const endpoint = createEndpoint({
methods: (method) => ({
get: method({
handler: (data, { res, failWithCode }) => {
const response = await fetch(
'https://w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf'
);
if (!response.ok) {
throw failWithCode(response.status, response.statusText);
}
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', 'attachment; filename=dummy.pdf');
await pipeline(await response.blob(), res);
return nothing;
},
}),
}),
});
export default endpoint.handler;
Parsing
To verify and optionally transform parts of the original request, you can provide parsers. These will receive the original data from a request, and are expected to return data that will be passed to the final handler, or throw an error.
Currently, you can provide parsers for body
and query
.
- TypeScript
- JavaScript
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
post: method<{ created: true }>()({
parsers: {
body: (body, failWithCode) => {
if (!body) {
throw failWithCode(400, 'no body provided');
}
if (typeof body !== 'string') {
throw failWithCode(400, 'invalid body');
}
return body; // now string
},
},
handler: ({ body }, { failWithCode, succeedWithCode }) => {
console.log(body); // typed as string thanks to parser
return succeedWithCode(201, { created: true });
},
}),
}),
});
export default endpoint.handler;
import { createEndpoint } from '../../src/api';
const endpoint = createEndpoint({
methods: (method) => ({
post: method()({
parsers: {
body: (body, failWithCode) => {
if (!body) {
throw failWithCode(400, 'no body provided');
}
if (typeof body !== 'string') {
throw failWithCode(400, 'invalid body');
}
return body; // now string
},
},
handler: ({ body }, { failWithCode, succeedWithCode }) => {
console.log(body); // typed as string thanks to parser
return succeedWithCode(201, { created: true });
},
}),
}),
});
export default endpoint.handler;
When using Typescript, it's required to use the double call syntax method<ReturnType>()(definition)
to use parsers.
This allows the ReturnType
type to be provided explicitly, while inferring the parsed types.
If you're using Javascript, this is not required.