Segment
Overview
Commonly used back-end frameworks that use the Controller-Service-Repository pattern implement the following hierarchical structure for the server code:
- Back-end sever - the highest level of the hierarchy, made of controllers.
- Controllers - the second level of the hierarchy, made of handlers.
- Handlers - the lowest level of the hierarchy, they perform request processing.
(We skip services since they don’t play a role in the hierarchy.)
Vovk.ts adds a new level to this hierarchy in between the back-end server and controllers:
- Back-end server - the highest level of the hierarchy, made of segments.
- Segments - the second level of the hierarchy, made of controllers.
- Controllers - the third level of the hierarchy, made of handlers.
- Handlers - the lowest level of the hierarchy, they perform request processing.
Each segment controls a specific path such as /api/foo
, /api/bar
etc. and compiled as a separate serverless function. This allows to split the back-end code into multiple smaller “back-ends” that are responsible for a specific area of the app, similar to how the front-end code is split into multiple pages in Next.js. Segments are implemented with the help of Next.js optional catch-all segments and initialised by calling initSegment
function in the route.ts file that is stored at [[…slug]] folders.
When NODE_ENV
env variable is set to "development"
, each segment route provides _schema_
endpoint that serves the schema of the segment and it’s invoked by dev CLI to retrieve the schema of the segment and build corresponding JSON files at .vovk-schema/ folder. This approach allows to skip importing native Node.js modules on the Next.js side and use export const runtime = 'edge'
in the route.ts file with no errors.
Segment priority
In case if you have multiple segments, more nested one is going to have higher priority. For example, if you have the following segments:
- /src/app/api/[[…slug]]/ for the root segment
- /src/app/api/foo/[[…slug]]/ for
foo
segment - /src/app/api/foo/bar/[[…slug]]/ for
foo/bar
segment
Then request to /api/foo/bar is going to have the highest priority and is going to be handled by the foo/bar
segment. If the request doesn’t match the foo/bar
segment but matches the foo
segment, it’s going to be handled by the foo
segment. If the request doesn’t match the foo
segment, it’s going to be handled by the root segment.
You can change the API folder name from api
to any other name by modifying config option rootEntry
.
Creating segments
Vovk.ts implements the segments via “Optional Catch-all Segment” defined as route.ts file in /src/app/api/[[…slug]]/ for root segment or /src/app/api/segment-name
/[[…slug]]/ folder other segments. It exports route handlers such as GET
, POST
etc, created by initSegment
function. The file can also export any of the Next.js segment config options such as runtime
or maxDuration
to determine behaviour of the Next.js route. If a segment is supposed to be available at front-end for RPC calls, it should also export Controllers
type that is used to type the generated TypeScript RPC library.
The module generator as well as this documentation uses vovk
as the slug name but it can be named in any valid way.
A segment is initialised by calling initSegment
function in the route.ts file. The function takes an object with the following properties:
controllers
- an object with the controllers that are going to be used in the segment. The keys of the object define the names of the generated RPC modules and the values are the controllers.segmentName
- the name of the segment. By default it’s an empty string that defines the root segment described below.emitSchema
- a boolean value that defines whether to emit the schema for the segment. By default it’strue
. If set tofalse
, the schema for the segment is not going to be emitted.exposeValidation
- a boolean value that defines whether to expose the validation data for the segment. By default it’strue
. If set tofalse
, the validation data is not going to be exposed.onError
- a function that is going to be called when an error occurs in the segment. The function takes an object with the following properties:error: HttpError
- the error that occurred.request: NextRequest
- the request object that allows to get headers, URL, etc.
The root segment
npx vovk new segment # will create a new segment at src/app/api/[[...vovk]]/route.ts
For simple single page apps you can use the root segment as the only segment. In this case, the back-end code of the app is going to be bundled into a single serverless function when deployed.
Here is an example of a route.ts file for a single-segment app:
import { initSegment } from 'vovk';
import UserController from '../../modules/user/UserController';
import PostController from '../../modules/post/PostController';
// maximum duration of request in seconds, useful for long-running JSONLines requests
export const maxDuration = 300;
const controllers = {
UserRPC: UserController,
PostRPC: PostController,
};
// export the controllers type to be used in the client code
export type Controllers = typeof controllers;
// export the Next.js route handlers
export const { GET, POST, PUT, DELETE } = initSegment({
controllers,
});
Schema for the root segment is going to be stored at .vovk-schema/root.json file.
The root
name is used for file naming purposes only. In all other cases, including configuration, the root segment
name is going to be an empty string.
Multiple segments
You can create multiple segments in your app to split the back-end code into multiple serverless functions. There might be multiple reasons for that:
- Split the back-end code into multiple serverless functions to reduce the size of the function bundle.
- Divide the back-end code into multiple areas e. g. root,
admin
,customer
,customer/public
etc. - Support different versions of the same API (e.g.
v1
,v2
). - Create a static segment for OpenAPI spec, historical data, etc.
Each segment is located in a nested folder determining the API path and the segment name. For example, a segment located in /src/app/api/segment-name
/[[…slug]]/ folder is going to be served at /api/segment-name
path. The level of nesting is unlimited.
For non-root segments initSegment
function requires to provide segment name as segmentName
option.
// ...
export const { GET, POST, PUT, DELETE } = initSegment({
segmentName: 'foo',
controllers,
});
The schema for the segment foo
is going to be stored at .vovk-schema/foo
.json file.
For segments of deeper nesting such as /src/app/api/foo/bar/baz
/[[…slug]]/ the segment name needs to be set to "foo/bar/baz"
and the schema is going to be stored at .vovk-schema/foo/bar/baz
.json file.