Express middleware with TS: void' is not assignable to parameter of type 'PathParams' - node.js

I'm new to typescript and converting my existing JS application. I'm stuck on what seems simple and I've tried searching existing posts to no avail.
I have a simple middleware in Express to log all requests:
const { logger } = require("winston");
import express, { Application, Request, Response, NextFunction } from 'express'
...
const app: Application = express();
...
// Middleware - Log all http requests
app.use((req: Request, res: Response, next: NextFunction) => {
logger.info(`${req.loggedInUser} user [${req.originalUrl}]`) ;
return next();
});
In typescript this errors with
The last overload gave the following error.
Argument of type '(req: Request, res: Response, next: NextFunction) => void' is not assignable to parameter of type 'PathParams'.
If it matters I've extended the Request object in index.d.ts:
declare module "express" {
export interface Request {
user: any;
loggedInUser: any;
oidc: any;
}
}
The logger is winston.
Things I've tried:
setting the output type of the function to void - didn't work.
adding "as express.RequestHandler" after logger similar to Express errors with typescript but no luck.
changing Request to type "any" - works but that of course is a poor workaround.
Adding path argument "/" - doesn't work thought the IDE (VSCode) no longer reports as an error. Compile fails though.
How can I fix this and what is the concept behind this?
Most express docs show using "app.use" without the path for things like helmet, cors etc so it seems like this should be common.
Thank you for your insights!

Try extending the Request object like this instead
declare global {
namespace Express {
interface Request {
user: any;
loggedInUser: any;
oidc: any;
}
}
}
as mentioned by basarat here: Extend Express Request object using Typescript.
I replicated your error message with my express app using your Request object extension and changing it to the above code fixed the issue.

Related

How do I add parameters to long-form return requests in module.exports routes?

I'm coding for an API connection area, that's predominately graphql but needs to have some REST connections for certain things, and have equivalent to the following code:
foo.js
module.exports = {
routes: () => {
return [
{
method: 'GET',
path: '/existing_endpoint',
handler: module.exports.existing_endpoint
},
{
method: 'POST',
path: '/new_endpoint',
handler: module.exports.new_endpoint // <--- this not passing variables
}
]
},
existing_endpoint: async () => {
/* endpoint that isn't the concern of this */
},
new_endpoint: async (req, res) => {
console.log({req, res})
return 1
}
}
The existing GET endpoint works fine, but my POST endpoint always errors out with the console of {} where {req, res} should have been passed in by the router, I suspect because the POST isn't receiving. I've tried changing the POST declaration in the routes to module.exports.new_endpoint(req, res), but it tells me the variables aren't found, and the lead-in server.js does have the file (it looks more like this...), and doing similar with the server.js, also getting similar results, implying that's probably wrong too. Also, we have a really strict eslint setup, so I can't really change the format of the call.
Every example I've seen online using these libraries is some short form, or includes the function in the routes call, and isn't some long form like this. How do I do a POST in this format?
/* hapi, environment variables, apollog server, log engine, etc. */
/* preceeding library inclusions */
const foo = require('./routes/foo')
const other_route = require('./routes/other_route')
const startServer = async () => {
const server = Hapi.server({port, host})
server.route(other_route.routes())
server.route(foo.routes())
}
This is a bug with Hapi in node v16. I just opened an issue.
Your current solutions are either:
Upgrade to Hapi v20
Use n or another method to downgrade to node v14.16 for this project. I can confirm that POST requests do not hang in this version.

Access model method inside express route (Loopback 4)

I will show you an example of what i'm trying to do :
server.ts
export class ExpressServer {
public readonly app: express.Application;
public readonly lbApp: ImportedApp;
private server?: Server;
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new ImportedApp(options);
this.app.get('/hello', async function (_req: Request, res: Response) {
//Here i'd like to call a model like User.findById() but can't figure out how to do it..
});
}
}
As you see in the comment i'm trying to access my models method to use them in my route (Like showing users informations on my view) But can't figure out how to do it. I'v already tryed to import the DataSource, the model, the controller but nothing's containing my methods (FindById, Create etc..)
If i find nothing i will have to use something like Axios or Request to request the ressource from the api instead of inside my code like await request('api/users/myusername)
In LoopBack 4, we use Repository design patter for accessing data. In order to find a user instance by its id, you need to obtain an instance of UserRepository via dependency injection. Quoting from https://loopback.io/doc/en/lb4/Repository.html:
Repositories are adding behavior to Models. Models describe the shape of data, Repositories provide behavior like CRUD operations. This is different from LoopBack 3.x where models implement behavior too.
UPDATED SOLUTION
To obtain an instance of a Repository class, you can use the Service Locator design pattern and get the instance from the per-request Context object provided by LoopBack's REST layer.
import {MIDDLEWARE_CONTEXT, RequestContext} from '#loopback/rest';
import {UserRepository} from '../repositories';
function expressHandler(req, res, next) {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get<UserRepository>('repositories.UserRepository');
const users = await userRepo.find({limit: 10});
// render your view
}
We are discussing how to make this use case easier to implement in GitHub pull request loopback-next#6793, feel free to join the discussion there.
ORIGINAL ANSWER
Instead of writing an Express route for your rendered pages, I recommend you to write a LoopBack 4 Controller instead; and inject Express Response object to allow you to render the HTML view, as explained in https://loopback.io/doc/en/lb4/Accessing-http-request-response.html#inject-http-response
import {Response, RestBindings, oas} from '#loopback/rest';
import {inject} from '#loopback/core';
import {UserRepository} from '../repositories';
export class PingController {
constructor(
#inject(RestBindings.Http.RESPONSE)
private response: Response
#repository(UserRepository)
public userRepository: UserRepository,
) {}
// Hide this endpoint from OpenAPI spec generated for the app
#oas.visibility('undocumented')
#get('/users')
list(): Response {
// Access User data via this.userRepository API
const users = await this.userRepository.find({limit: 10});
// Access the response object via `this.response`
this.response.render('users', {users});
// Return the HTTP response object so that LoopBack framework skips the
// generation of HTTP response
return this.response;
}
}
Having said that, if you already know how to access DataSource instances from your LB4 app in your Express routes, then you can instantiate Repository classes manually from your routes too:
const db = // your datasource
this.app.get('/hello', async function (_req: Request, res: Response) {
const repo = new UserRepository(db);
const users = await this.userRepository.find({limit: 10});
});
To me the solution is not working. Started from the express-composition example, i just need to access lb repositories from a generic express route outside of the lb4 request handler:
constructor(options: ApplicationConfig = {}) {
this.app = express();
this.lbApp = new NoteApplication(options);
this.lbApp.basePath('')
// Expose the front-end assets via Express, not as LB4 route
this.app.use('/api', this.lbApp.requestHandler);
this.app.get('/hello', async (req: Request, res: Response) => {
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
const userRepo = await ctx.get('repositories.UserRepository');
res.send('Hello world!');
});
}
the ctx in the line
const ctx = (req as any)[MIDDLEWARE_CONTEXT];
is always undefined.
My main goal is to have routes not under /api that can still access lb4 repositories.

How can I use axios or any other http request client package in my Adonisjs Controller

I want to call to a external API to get some data while I'm inside the controller.
Let's say, There is two different project, one is hosted in example.com and the other one is in a.example.com.
Now, when I am on a.example.com sub-domain, I want the user data who has logged in example.com in that sub-domain.
For testing purpose, I set an API url for the in example.com which will return the expected json data.
My Problem is, I want to call axios to that API from the a.example.com domain. But I can not require the axios package inside my controller.
Here is my controller of a.example.com:
'use strict'
const axios = require('axios')
class UserController {
async getAllUser({ request, auth, response, axios }) {
await axios('http://a.example.com/getUser')
.then(response => {
return response;
});
}
}
export default UserController
But I get this error :
'import' and 'export' may appear only with 'sourceType: module' export default UserController
I don't know how to solve this, can anyone help me fix this?
In Adonis, you use "use" instead of require.
const Axios = use('Axios');
Instead of
export default UserController
Use
module.exports = UserController

Why in DefinitelyTyped `http.IncomingMessage` defined as interface, not a class?

As said documentation of Node.js, http.IncomingMessage is a class, not an interface. So why in DefinitelyTyped http.IncomingMessage defined as interface?
Now in my code I can not do this:
import * as http from 'http';
let Request = http.IncomingMessage;
TypeScript error:
[ts] Property 'IncomingMessage' does not exist on type 'typeof "http"'.
I'm doing something wrong?
I see that really not exists Node.js API for extends http.IncomingMessage. This way more as hack and not normal extends:
http.IncomingMessage.prototype.absoluteUri = function absoluteUri(path) {
// ...
};
I found this answer to extends http.IncomingMessage:
var extendedRequest = {
get userAgent() {
this.request.headers['user-agent'];
}
}
createServerCallback(function (req, res) {
var request = Object.create(extendedRequest);
request.request = req;
});

extending express application interface

I am trying to extend the expressjs Application interface using declaration merging as explained in the express type definitions
declare module Express {
// These open interfaces may be extended in an application-specific manner via declaration merging.
// See for example method-override.d.ts (https://github.com/borisyankov/DefinitelyTyped/blob/master/method-override/method-override.d.ts)
export interface Request { }
export interface Response { }
export interface Application { }
}
So, my app.ts looks like this:
/// <reference path="typings/express/express.d.ts" />
declare module Express {
export interface Application {
testA: string;
}
export interface Request {
testR: string;
}
}
import express = require('express');
var app = express();
app.testA = "why not?";
app.use(function (req, res, next) {
req.testR = "xxx";
})
I get the errors:
"Property testA does not exist on type Express"
"Property testR does not exist on type Request"
Any clues?
Since you are using modules, declaration merging won't happen here. In app.ts there isn't an Express module to merge with so it's making a completely separate module definition. You need to move the code...
declare module Express {
export interface Application {
testA: string;
}
export interface Request {
testR: string;
}
}
...into a .d.ts file so that the interfaces are merged with the ones in express.d.ts.

Resources