Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
@anephenix/fastify-resource
Advanced tools
A way of creating RESTful CRUD resources for Fastify and Objection.js.
npm i @anephenix/fastify-resource
When writing code for an API, you may find yourself generating RESTful routes for Objection.js models that support CRUD operations (Create/Read/Update/ Delete).
This library provides a way to generate code that will provide that with a few lines of code.
For example, in Fastify you will usually define routes and handler functions for CRUD like this:
const fastify = require('fastify')({ logger: false });
const Asset = require('./models/Asset');
// GET /assets
fastify.get('/assets', async (req, rep) => {
try {
const data = await Asset.query();
return data;
} catch (error) {
rep.statusCode(400);
return error.message;
}
});
// POST /assets
fastify.post('/assets', async (req, rep) => {
try {
const assets = await Asset.query().insert(req.body);
rep.statusCode(201);
return assets;
} catch (error) {
rep.statusCode(400);
return error.message;
}
});
// GET /assets/:id
fastify.get('/assets/:id', async (req, rep) => {
try {
const asset = await Asset.query().findById(req.params.id);
if (asset) return asset;
if (!asset) {
res.statusCode(404);
return 'Not found';
}
} catch (error) {
rep.statusCode(400);
return error.message;
}
});
// PATCH /assets/:id
fastify.patch('/assets/:id', async (req, rep) => {
try {
const asset = await Asset.query().patchAndFetchById(
req.params.id,
req.body
);
if (asset) return asset;
if (!asset) {
res.statusCode(404);
return 'Not found';
}
} catch (error) {
rep.statusCode(400);
return error.message;
}
});
// DELETE /assets/:id
fastify.delete('/assets/:id', async (req, rep) => {
try {
await Asset.query().deleteById(req.params.id);
return req.params.id;
} catch (error) {
rep.statusCode(400);
return error.message;
}
});
There's about 60 lines of code there for the routes. Now if you have a lot of resources that you want to generate a bunch of RESTful routes and CRUD actions for, then this file will become hundreds of lines of code at the very least. Also, you may find that you are repeating the same code but with different Objection.js models and routes in place.
With fastify resource, you can write this code and it will do the same thing:
const fastify = require('fastify')({ logger: false });
const Asset = require('./models/Asset');
const { resource, attach } = require('@anephenix/fastify-resource');
const { routes } = resource(Asset, 'asset');
attach({ fastify, routes });
The resource
function is passed the Objection.js model as the 1st argument,
and the name of the resource for generating the url routes from as the 2nd
argument.
This will do the following:
GET /assets
POST /assets
GET /assets/:id
PATCH /assets/:id
DELETE /assets/:id
To then attach those routes to the fastify app, we call the attach
function
and pass the fastify instance and routes to combine them.
This helps you to quickly assemble a REST API for your Objection.js models, using a few lines of code.
The way that the library works is that it generates a set of objects that provide functions that can be called:
Route -> Controller -> Service -> Model
The first is the set of routes
, which when generated for a resource look
something like this:
[
{ method: 'get', url: '/assets', handler: Function },
{ method: 'post', url: '/assets', handler: Function },
{ method: 'get', url: '/assets/:id', handler: Function },
{ method: 'patch', url: '/assets/:id', handler: Function },
{ method: 'delete', url: '/assets/:id', handler: Function },
];
The handler
function is the controller action that will be called when
the API route is called. The controller is accessible from the resource
function call:
const { routes, controller, service } = resource(Asset, 'asset');
The generated controller code looks like this:
{
index: async (req:Request, rep:Reply) => {
const { success, data, error } = await service.getAll(req.params);
return handleResponse({success,data,error, rep});
},
create: async (req:Request, rep:Reply) => {
const params = Object.assign({}, req.params, req.body);
const { success, data, error } = await service.create(params);
return handleResponse({success,data,error, rep, successCode: 201});
},
get: async (req:Request, rep:Reply) => {
const { success, data, error } = await service.get(req.params);
return handleResponse({success,data,error, rep});
},
update: async (req:Request, rep:Reply) => {
const params = Object.assign({}, req.params, req.body);
const { success, data, error } = await service.update(params);
return handleResponse({success,data,error, rep});
},
delete: async (req:Request, rep:Reply) => {
const { success, data, error } = await service.delete(req.params);
return handleResponse({success,data,error, rep});
},
};
The controller
actions try to extract the relevant data from the HTTP
request and pass it to the relevant service action. It then handles the
response from the service, and returns the HTTP response suitable for it.
This makes the controller action essentially an interface to the service, and the service is where the business logic for the action is defined.
The service is the core part of the application's business logic, and is the primary part of the code that interfaces with the Objection.js model. The generated code for the service looks like this:
{
getAll: async (params:Params) => {
try {
const data = await model.query().where(params);
return { success: true, data };
} catch (error) {
return { success: false, error };
}
},
create: async (params:Params) => {
try {
const data = await model.query().insert(params);
return { success: true, data };
} catch (error) {
return { success: false, error };
}
},
get: async (params:Params) => {
try {
const data = await model.query().where(params);
return { success: true, data };
} catch (error) {
return { success: false, error };
}
},
update: async (params:Params) => {
try {
const updateParams = objectWithoutKey(params, 'id');
const data = await model
.query()
.patchAndFetchById(params.id, updateParams);
return { success: true, data };
} catch (error) {
return { success: false, error };
}
},
delete: async (params:Params) => {
try {
await model.query().deleteById(params.id);
return { success: true, data: params.id };
} catch (error) {
return { success: false, error };
}
},
};
Usually controller actions would be the primary code that interfaces with the Objection.js model (like in the MVC pattern), but by abstracting out that part into a service layer, you then have the opportunity to re-use those service actions with other interfaces, such as:
We essentially treat the controller actions as supporting a HTTP REST API interface to the service actions, alongside the other options.
Any REST API tends to implement a hierarchy of resources. Let's say for example there are 2 models - Post and Comment. A post has many comments, and we want to create an API that fits that relationship. We might want our comments API routes to be nested under the posts API routes.
The library can do that. In the resource
function, you can pass an array of
resources to assemble the API routes that fit that. Below is an example of
how to do that:
const fastify = require('fastify')({ logger: false });
const Post = require('./models/Post');
const Comment = require('./models/Comment');
const { resource, attach } = require('@anephenix/fastify-resource');
const postResource = resource(Post, 'post');
const commentResource = resource(Comment, ['post', 'comment']);
attach({ fastify, routes: postResource.routes });
attach({ fastify, routes: commentResource.routes });
This will make the following API routes available on the fastify instance:
GET /posts
POST /posts
GET /posts/:id
PATCH /posts/:id
DELETE /posts/:id
GET /posts/:post_id/comments
POST /posts/:post_id/comments
GET /posts/:post_id/comments/:id
PATCH /posts/:post_id/comments/:id
DELETE /posts/:post_id/comments/:id
You can have many levels of nested resources in your code, it is not limited to any number (we just showed 2 resources in order to demonstrate the example).
There might be cases where you need to do custom adjustments to the controller, or where you may want to access the service generated for the resource to attach other interfaces to.
The library allows you to generate the services, controllers and routes separately, so that you can structure your code in whatever pattern suits you.
The library has these functions available:
const {
serviceGenerator,
controllerGenerator,
resourceRoutes,
} = require('@anephenix/fastify-resource');
The serviceGenerator
function will generate a service for an Objection.js
model.
The controllerGenerator
function will generate a controller for a service.
The resourceRoutes
function will generate the routes for a controller and
the list of resources to turn into urls.
Using these will allow you to generate parts of the RCS pattern as well as make custom parts of that code if needed.
npm t
©2025 Anephenix OÜ. All Rights Reserved.
FAQs
A way for generating resources using fastify and objection
The npm package @anephenix/fastify-resource receives a total of 65 weekly downloads. As such, @anephenix/fastify-resource popularity was classified as not popular.
We found that @anephenix/fastify-resource demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.