using fastify plugin inside a fastify plugin - fastify

According the documentation creating a plugin with fastify-plugin will not encapsulate and add the plugin content to the "global" fastify scope.
including the dbconnector plugin inside the db plugin doesn't add the inner plugin into the fastify scope.
works
server.js
fastify.register(import("./plugins/dbconnector.mjs"));
fastify.register(import("./plugins/db.mjs"), { prefix: "/db" });
doesn't
server.js
fastify.register(import("./plugins/db.mjs"), { prefix: "/db" });
plugins/db.mjs:
import fastifyPlugin from "fastify-plugin";
async function plugin(fastify, opts) {
fastify.register(import("./plugins/dbconnector.mjs"));
...
dbconnector.mjs (default documentation example)
import fastifyPlugin from "fastify-plugin";
async function dbConnector(fastify, options) {
fastify.register(import("fastify-mongodb"), {
url: "mongodb://...",
});
}
export default fastifyPlugin(dbConnector);

Related

Fastify - i18next in custom error handler

In my Node.js application I´m using fastify as framework together with some plugins (i18next).
I´m using i18next for translation (is working properly in preHandler and handler hooks) and want to customize all errors by using my translations via i18next in my custom error handler (via fastify setErrorHandler method).
Here is my coding so far (from top to bottom):
import fastify from "fastify";
import routes from "./routes/routes.js";
import i18next from "./config/i18next.js";
import i18nextMiddleware from "i18next-http-middleware";
const app = fastify({
logger: true,
ajv: {
customOptions: {
allErrors: true,
$data: true
}
},
});
app.register(i18nextMiddleware.plugin, { i18next });
app.register(routes, { prefix: '/api/v1' });
app.setErrorHandler((error, request, reply) => {
console.log('request.t: ', request.t);
if (error.validation) {
// other coding ...
reply.send(error);
}
});
(async () => {
try {
await app.listen(process.env.PORT);
console.log(`Server started listening on port ${process.env.PORT}`);
} catch (err) {
app.log.error(err);
}
})();
Inside the setErrorHandler (which is sync) I want to use the initialized t() method from i18next instance passed to request object (this is working for all my routes in the preHandler and handler hooks) but is not working in the setErrorHandler, as I´ll get undefined when an error occours.
I know the setErrorHandler is sync and all plugin registration will be handled async, but didn´t solved it yet.
What I´ve also tried is to do the setErrorHandler call in the after() hook when registering the i18next plugin, but the result is the same. I know I´m missing a small detail, but any tips are appreciated, as I´m spinning my head around this since hours.
This happens because the i18next-http-middleware plugin adds the t method to the request object on a preHandler hook that is executed after the JSON validation step:
export function plugin (instance, options, next) {
const middleware = handle(options.i18next, options)
instance.addHook('preHandler', (request, reply, next) => middleware(request, reply, next))
return next()
}
source
You should be able to write a workaround like this:
import i18nextMiddleware from "i18next-http-middleware";
// ....
const middleware = i18nextMiddleware.handle({})
app.addHook('preValidation', (request, reply, next) => middleware(request, reply, next))
I think that is a bug of the module tho

typedi + fastify - initialize service asynchronously

I'm working on a nodejs fastify based app service and using typedi for dependency injection.
Some services I use need async initialization.
MyService.ts
export class MyService {
constructor() {
}
public async init() {
....
}
}
I am trying to initialize the service at application startup so that any service doing Container.get(MyService) gets this initialized instance of MyService
app.ts
export default async function(fastify: FastifyInstance, opts: Options, next: Function) {
// This loads everything under routes
fastify.register(autoload, {
dir: path.join(__dirname, "routes"),
options: opts,
includeTypeScript: true,
});
await Container.get(MyService);
next();
}
server.ts
import app from "./app";
const server = fastify({
logger: logger
});
server.register(oas, docs);
server.register(app);
server.ready(err => {
if (err) throw err;
server.oas();
});
server.listen(config.port, (err) => {
if (err) {
server.log.error(err);
process.exit(1);
}
server.log.info(`server listening on ${server.server.address()}`);
});
export default server;
My attempt to initialize MyService is failing.
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"
Any hints to what I'm doing wrong? I'm new to nodejs and would really appreciate sample code that is correct for this scenario.
Edit
I tried import
Container.import([CmkeService]);
MissingProvidedServiceTypeError [ServiceNotFoundError]: Cannot determine a class of the requesting service "undefined"

Load custom configuration runtime

I have a nuxt application in which I will need to append data from a generated configuration file when the application is first started. The reason I cannot do this in the actual build is because the configuration file does not exists at this point; it is generated just before calling npm start by a bootstrap script.
Why don't I generated the configuration file before starting the application you may ask and this is because the application is run in a docker container and the built image cannot include environment specific configuration files since it should be used on different environments such as testing, staging and production.
Currently I am trying to use a hook to solve this, but I am not really sure on how to actually set the configuration data in the application so it can be used everywhere:
# part of nuxt.config.js
hooks: {
listen(server, listener) {
# load the custom configuration file.
fs.readFile('./config.json', (err, data) => {
let configData = JSON.parse(data));
});
}
},
The above hook is fired when the application first starts to listen for connecting clients. Not sure this is the best or even a possible way to go.
I also made an attempt of using a plugin to solve this:
import axios from ‘axios’;
export default function (ctx, inject) {
// server-side logic
if (ctx.isServer) {
// here I would like to simply use fs.readFile to load the configuration, but this is not working?
} else {
// client-side logic
axios.get(‘/config.json’)
.then((res) => {
inject(‘storeViews’, res.data);
});
}
};
In the above code I have problems both with using the fs module and axios.
I was also thinking about using a middleware to do this, but not sure on how to proceed.
If someone else has this kind of problem here is the solution I came up with in the end:
// plugins/config.js
class Settings
{
constructor (app, req) {
if (process.server) {
// Server side we load the file simply by using fs
const fs = require('fs');
this.json = fs.readFileSync('config.json');
} else {
// Client side we make a request to the server
fetch('/config')
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((json) => {
this.json = json;
});
}
}
}
export default function ({ req, app }, inject) {
inject('config', new Settings(app, req));
};
For this to work we need to use a server middleware:
// api/config.js
const fs = require('fs');
const express = require('express');
const app = express();
// Here we pick up requests to /config and reads and return the
// contents of the configuration file
app.get('/', (req, res) => {
fs.readFile('config.json', (err, contents) => {
if (err) {
throw err;
}
res.set('Content-Type', 'application/json');
res.end(contents);
});
});
module.exports = {
path: '/config',
handler: app
};

Typescript Error: Property 'user' does not exist on type 'Request'

I have the following piece of code in my express app
router.get('/auth/userInfo', this.validateUser, (req, res) => {
res.json(req.user);
});
and my IDE seems to be complaining with the error
error TS2339: Property 'user' does not exist on type 'Request'.
When I compile my typescript code it seems to be throwing this error. Any ideas why this is happening?
We have a large API written in Express and Typescript, and this is how we handle such scenarios:
We keep the request definitions in one file:
import { Request } from "express"
export interface IGetUserAuthInfoRequest extends Request {
user: string // or any other type
}
And then in the file where we are writing the controller functions:
import { Response } from "express"
import { IGetUserAuthInfoRequest } from "./definitionfile"
app.get('/auth/userInfo', validateUser, (req: IGetUserAuthInfoRequest, res: Response) => {
res.status(200).json(req.user); // Start calling status function to be compliant with Express 5.0
});
Be advised that "user" is not a property that is available natively in the Request object of Express. Make sure that you are using a middleware that adds such property to the request object.
req is probably of type Request from "express" package and user does not exist there. You have to either extend Request with own router handler or cast it to type any or object.
try res.json(req['user']) or res.json( (<any>req).user )
You may also use module/global augmentation
import { Request } from "express"
declare module "express" {
export interface Request {
user: any
}
}
newer express definition may need to augment the core def instead
declare module 'express-serve-static-core' {
export interface Request {
user: any
}
}
You can also make your own handler wrapper (instead of extending Router functionality in ExpressJs).
import * as express from 'express';
interface IUserRequest extends express.Request {
user: any
}
function myHandler(handler: (req: IUserRequest, res: express.Response, next?: express.NextFunction) => any) {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
try {
validateUser(req, res, (err) => { // your validateUser handler that makes a user property in express Request
if(err)
throw err;
let requestWrapper: IUserRequest = <IUserRequest>req;
handler(requestWrapper, res, next);
})
}
catch (ex) {
next(ex);
}
}
}
let app = express();
// init stuff for express but this.validateUser handler is not needed
app.use('/testuser', myHandler((req, res) => {
res.json(req.user);
}));
UPDATED:
Since Typescript is evolving I would also consider using Type Guards
if (hasUser(req)) {
console.log(req.user)
}
function hasUser(request: Request): request is Request & { user: number } {
return 'user' in request && typeof request['user'] == 'number'
}
You need to make a Declaration Merging:
"Declaration merging means that the compiler merges two separate
declarations declared with the same name into a single definition."
To do that you can create a file called types.d.ts at your project src folder (or wherever you want) with the following content:
declare namespace Express {
export interface Request {
user: any;
}
export interface Response {
user: any;
}
}
Here we are telling the compiler to add user propertie to our Request and Response definiton.
Next, we need to attach this to our tsconfig.json.
Example:
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"pretty": true,
"sourceMap": true,
"target": "es6",
"outDir": "./dist",
"baseUrl": "./lib"
},
"include": [
"lib/**/*.ts"
],
"exclude": [
"node_modules"
],
"files":["types.d.ts"]
}
Now, the typescript compiler know that Request, which has a property called user that in my case, can accept any json object. You can restrict the type for string if you want.
You're getting this error because there's no type definition for the user property in the the native express Request object. You should install the type definitions for the middleware you're using to add user to the request.
For example, if you're using the passport library for JWT authentication as middleware:
router.get('/auth/userInfo', passport.authenticate('jwt', {session:false}), (req, res, next) => {
// Get their info from the DB and return it
User.findOne({ email: req.user.email }, (err, user) => {
if (err) {return next(err);}
...
...
You should add the type definitions for passport:
npm install --save #types/passport
create new folder named types inside "src" folder
create file <filename>.d.ts in types folder
add following code:
declare namespace Express {
export interface Request {
user: any
}
}
you will notice that error disappeared
inside section typeRoots add the following to tsconfig.json file:
"typeRoots": [
"./node_module/#types",
"./src/types"
],
you can use type Request of express like this:
import { Request, Response } from "express";
router.get('/auth/userInfo', this.validateUser, (req: Request, res: Response) => {
res.json(req.user);
});
if you use ts-node you will get the same error when you try to execute your .ts file because ts-node ignore .d.ts fies unless you add flag --files to package.json script like this:
"scripts": {
.
.
"dev": "ts-node --files src/app.ts"
}
for more about this topic you can see this repository that created by Microsoft team.
If you're using ts-node and not ts-node-dev, do this:
Create a typings folder in your src folder.
Create a folder within the typings folder with the name of the package you intend to extend.
Create an index.d.ts file with the new folder.
In my case, I am extending express, I ended up with something like this:
src/
- typings/
- express/
- index.d.ts
within the index.d.ts file I have this:
declare module Express {
export interface Request {
bucketUrl: string;
fileName: string;
}
}
Remember to update your .tsconfig:
{
"compilerOptions": {
"typeRoots" : ["./node_modules/#types", "./typings"]
}
}
For people using GraphQL, there comes a problem with the graphqlExpress function not accepting your new interface. I was attempting to follow this guide until I ran into this exact problem. I have figured out this solution:
this.app.use("/graphql", bodyParser.json(), this.auth, graphqlExpress((req: AuthRequest | undefined) => ({
schema,
context: {
user: req!.user
}
})));
And in the AuthRequest interface:
import {Request} from "express"
export interface AuthRequest extends Request {
user?: string
}
Typescript will not allow you to use AuthRequest unless it is also allowed to be undefined. Therefore the | undefined. (source)
After this point, the user object does not 100% exist on the Request object, hence the ? after user.
In my case, using ? to solve the problem.
import { Request } from "express";
interface MyUserRequest extends Request {
// Use `user?:` here instead of `user:`.
user?: string;
}
router.get('/auth/userInfo', (req: MyUserRequest, res) => {
res.status(200).json(req.user)
});
Just extend "Request" without declaration.
I was using okta-angular-node with express for this https://developer.okta.com/blog/2018/10/30/basic-crud-angular-and-node
I came up with similar error for req.user. Please check the server/auth.ts inside the above given link.
Type casting worked for me. Try to cast as follows
req.user to (req['user'])
Add a typeRoots to your tsconfig.json, this will tell typescript where to look to find declaration files. By default typescript looks in /node_modules/#types, but when you specify this property, those defaults are cleared. You can read more here.
tsconfig.json
{
"compilerOptions": {
"typeRoots": ["./node_modules/#types", "./src/util"]
}
}
types.d.ts
import { User } from '../models/user'
declare global {
namespace Express {
interface Request {
user: User
}
}
}
Folder structure
node_modules
tsconfig.json
/src/
/models/
user.ts
/util/
types.d.ts
You can try including the below snippet in your middleware.ts file:
declare module 'express-serve-static-core' {
interface Request {
user?: string
}
}
Because the "user" property doesn't exist in the native express "Request" object.
There are various workarounds for this issue.
1- Simplest one - ... as any
router.get('/auth/userInfo', this.validateUser, (req, res) => {
res.json((req as any).user);
})
2- Add the following code to the app.js or interface file once.
declare module "express-serve-static-core" {
interface Request {
user: any;
}
}
declare global {
namespace Express {
interface Request {
user?: any
}
}
}
Old question but if anyone stumbles across this like I did, take a look at 'Declaration Merging' - solved the issue for me.
https://www.typescriptlang.org/docs/handbook/declaration-merging.html
https://truetocode.com/extend-express-request-and-response-typescript-declaration-merging/
You need to decorate the request using fastify decorators as mentioned below,
fastify.decorateRequest('user', <pass null or empty string here>)
and handle what should be in the user object.
Official document - https://www.fastify.io/docs/latest/Decorators/
in my case I had to do this
interface RequestUserAuth extends Request {
user?: {
id: string
role: string
// or any type you want but you must to use it like that "user?: etc..."
}
}
app.get('/', auth, (req: RequestUserAuth, res: Response) => {
console.log(req.user)
})
Just do
import { Request, Response} from "express";
then do this;
router.get('/auth/userInfo', this.validateUser, (req:any, res:Response) => { res.json(req.user); });
I had a working index.d.ts using other tips here but the fact that it was in the same folder as an unrelated index.ts was causing it to not work. I moved index.d.ts to its own types folder and the types started to get used. I'm guessing there was a collision with index.ts being at the same level.
Just going to share my types/index.d.ts in case it helps someone who is also using Prisma
import { User as PrismaUser } from "../prisma/prismaclient";
declare module "passport" {
namespace Express {
interface User extends PrismaUser {}
}
}
declare module 'express-serve-static-core' {
interface Request {
user?: PrismaUser
}
interface Response {
user?: PrismaUser
}
}
declare module "express-session" {
interface SessionData {
user: PrismaUser;
}
}

Hapi.js load plugins in order

I am new to Hapi.js and I am stuck a point trying to figure out how can we load plugins in order in a Hapi.js setup.
For example: I have 2 plugins Plugin1 and Plugin2. Lets say Plugin2 is dependent on Plugin1 and cannot run until Plugin1 is executed.
It seems like loading these plugins in 2 separate server.register methods or with a single server.register (with array of plugins) seems to be executing the plugins code in parallel...
So, can some one help me with how can I load plugins in order...thanks in advance
You will want to use server.dependency as a solution.
With it, you can declare a plugin dependent upon another and if the dependency is missing (or you accidentally create a circular dependency) your server will throw.
With this, you have an opportunity to use the after function to delay execution of code in Plugin2 that must wait until Plugin1 is loaded.
You can also see discussion titled "Inconsistent plugin load order" on the glue github repo or this one called "Server start can fail due to plugins not loaded in the right order" from aqua for additional info and details.
You have a few options available to you.
You could take a look at Glue. You can use the array syntax for plugins to load plugins in a specific order:
var Glue = require('glue');
var manifest = {
server: {
cache: 'redis'
},
connections: [
{
port: 8000,
labels: ['web']
},
{
port: 8001,
labels: ['admin']
}
],
plugins: [
{ 'Plugin1': null },
{ 'Plugin2': null }
]
};
var options = {
relativeTo: __dirname
};
Glue.compose(manifest, options, function (err, server) {
if (err) {
throw err;
}
server.start(function () {
console.log('Hapi days!');
});
});
This is same as doing the following without the use of Glue:
server.register(require('Plugin1'), function (err) {
server.register(require('Plugin2'), function (err) {
server.start(function () {
console.log('Hapi days!');
});
});
});
Having order-dependent plugins is messy though and hapi offers a better way to fix this. You can use server.dependency() to explictly express a plugin's dependency on another plugin. So inside Plugin2 you could do:
var ready = function (server, next) {
server.route({
...
});
next();
};
exports.register = function (server, options, next) {
server.dependency('Plugin1', ready);
next();
};
exports.register.attributes = {
name: 'Plugin2',
version: '0.0.1'
};
With this approach, it doesn't matter about the plugin registration order. This is great for big apps where there's lot of plugins worked on by different people or teams.
You can register your plugins in order after you created your server instance.
For example:
const server = new Hapi.Server(ConfigUtils.resolve(Config.Server));
await server.register([
PluginUtils.bootstrap(AwsPlugin, ConfigUtils.resolve(Plugin.Aws) ),
PluginUtils.bootstrap(OrmPlugin, ConfigUtils.resolve(Plugin.Orm) ),
PluginUtils.bootstrap(SessionPlugin, ConfigUtils.resolve(Plugin.Session) ),
]);

Resources