How does one use Routes in Express?

I’m a bit stumped as to how I should define a Route in Haxe. All good in JavaScript, but the Haxe syntax for defining a route escapes me because I have no idea how to instantiate a route. I tried all sorts of weird syntax such as

var storyRouter: Router = Express.Router().get(x, (req, res, next) → {});

I’ve seen an example on GitHub:

but it doesn’t work for the externs I’ve generated with dts2hx.

Trying to do

router = new Router();

Results in the mother of all verbose errors (it’s 6 KB in length :smiley: ).

Managed to get this to compile:

var storyRouter: express_serve_static_core.Router = Express.Router();

But have no idea how to use it. This, for example, fails with “no suitable overload”.

storyRouter.get("/story", (req, res, next) ->
        {
            res.end("Index");
        });

I’m obviously not very good at understanding how to call externs yet.

@Kyliathy please take a look at this externs hxexpress/Router.hx at master · abedev/hxexpress · GitHub. It worked fine when I tried it.

The Secret is in two things

  1. @:jsRequire("express", "Router") means we need to get “Router” property of require(“express”) object.
  2. @:selfCall function new(?options : RouterOptions) : Void; means calling constructor without new keyword.
    and after compilation we’ll get something like this:
    var router = require('express').Router();
1 Like

Translating the example from Express routing

In js we have

var router = express.Router()

In haxe, because Router is a type, it can’t be called as a function like in js, so we use a specially generated call method. The static version of call here turns out to be call_() (because there’s already another non-static method with the same name). So this line becomes

var router = express.Router.call_();

The remaining lines are relatively straight forward, however haxe has stricter function unification so if the function type is : (A, B, C) -> D, you must pass it a function with 3 parameters that returns a value of type D. TypeScript will not complain if you miss parameters, so in TS (a, b) => { ... } will work. The get and use callbacks all have 3 parameters and return Dynamic. So we add the missing parameter (next) and add return null because in haxe, Void cannot unify with Dynamic so the callbacks are expecting something to be returned

var router = express.Router.call_();

// middleware that is specific to this router
router.use(function timeLog (req, res, next) {
	js.Browser.console.log('Time: ', Date.now());
	return null;
});

router.get('/', function (req, res, next) {
	return null;
});
// define the home page route
router.get('/', function (req, res, next) {
	res.send('Birds home page');
	return null;
});
// define the about route
router.get('/about', function (req, res, next) {
	res.send('About birds');
	return null;
});

I like the pattern in the handwritten externs of using new Router() rather than call() so I’ll look into making that the default in dts2hx

Edit: Investigated automatically replacing static call() with new, turns out it doesn’t work well in the general case as this pattern is often not synonymous with new (although in this specific case it is), so I’ll keep the generator as-is

1 Like

Well, except I can’t seem to be able to invoke that call_() function.

var router = express.Router.call_();

It says express.Router is not a value. When I go to the definition of Router (as imported by import express.Router) I do see the function:

package express;

typedef Router = {
	@:selfCall
	function call_(req:express_serve_static_core.Request<express_serve_static_core.ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>, res:express_serve_static_core.Response<Dynamic, Float>, next:express_serve_static_core.NextFunction):Dynamic;

And I have a side-question here too. Why does Express.call return express_serve_static_core.Express? This is from the “main” Express extern, the one in the root of the package:

@:jsRequire("express") @valueModuleOnly extern class Express {
	/**
		Creates an Express application. The express() function is a top-level function exported by the express module.
	**/
	@:selfCall
	static function call():express_serve_static_core.Express;

I think you must have an older version of dts2hx; Router should not be a typedef:

npm install dts2hx express @types/express
npx dts2hx express

And then express/Router.hx is

package express;

@:jsRequire("express", "Router") extern class Router {

Which works as expected

The express_serve_static_core thing is how the express type definitions are defined for some reason – express is split up into separate dependencies, can’t say I’d do it that way myself but it’s a less obvious separation in typescript – I’ve got a plan to merge types between dependencies more as the next main roadmap item which may make this cleaner in the future

1 Like

You’re right. I had generated that package using an older dts2hx. I updated and now it works :).

Still having trouble linking the routes to the application because app.use doesn’t have a definition that supports me sending a Router.

var storyRouter: express_serve_static_core.Router = Router.call_();
storyRouter.get("/story", function(req, res, next)
{
	res.end("Index");
	return null;
});
app.use("/story", storyRouter);

These are the overloads for app.use:

	@:overload(function(handlers:haxe.extern.Rest<RequestHandlerParams<ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>>):Express { })
	@:overload(function<P, ResBody, ReqBody, ReqQuery>(path:PathParams, handlers:haxe.extern.Rest<RequestHandler<P, ResBody, ReqBody, ReqQuery>>):Express { })
	@:overload(function<P, ResBody, ReqBody, ReqQuery>(path:PathParams, handlers:haxe.extern.Rest<RequestHandlerParams<P, ResBody, ReqBody, ReqQuery>>):Express { })
	@:overload(function(path:PathParams, subApplication:Application):Express { })
	dynamic function use(handlers:haxe.extern.Rest<RequestHandler<ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>>):Express;

Ahh I see what’s happening there – in ts, Router is both a function type and an interface type. In haxe it has to be one or the other. Here, app.use() wants Router to be a function type ‘RequestHandler’. The trick we used was to add @:selfCall to Router so that it can have a function type associated, and the same works here; try this instead

app.use('/story', storyRouter.call)

I’ve added this to the dts2hx express example (and verified everything works as expected)

Maybe I can use an abstract wrapper around typedefs to enable automatic unification with the selfCall (this is the same idea as I mentioned here Working with externs for the popular Winston logger (NodeJS) - #2 by haxiomic so it will take a while to complete)

1 Like

Ohboy ohboy! Guruman @haxiomic on the case :smiley: It works!

Those call calls man :smiley: Always gettin’ the better of me. I would say I still haven’t grasped some essentials about externs.

What I find a bit puzzling is that the storyRouter.call you pointed me to, returns Dynamic, and from what I see no overload of use accepts Dynamic as the 2nd argument. There’s one overload that gets a subApplication: Application, is this the one that is actually used? Maybe I should launch a debug session and spend an hour paying attention to how calls go, also directly on the JS output, but trust me when I say, this month has broken records in how much stuff I had to deal with.

Happy that you have an even smoother way in mind, especially as such a feature could unblock other externs such as Winston (for the same reason as above, I just couldn’t muster the energy to poke the developers to update their TS files especially as there were other possible problems in the dark… so happy log4js worked… it’s perhaps a bit old skool but it does the job).

use() accepts a RequestHandler as the second argument (actually it’s as many RequestHandlers as you like because is a Rest argument). That has type

typedef RequestHandler<P, ResBody, ReqBody, ReqQuery> = (req:Request<P, ResBody, ReqBody, ReqQuery>, res:Response<ResBody, Float>, next:NextFunction) -> Dynamic;

Router’s call() has type

function call(req:Request<ParamsDictionary, Dynamic, Dynamic, qs.ParsedQs>, res:Response<Dynamic, Float>, next:NextFunction):Dynamic;

Which aside from a few Dynamics here and there (Dynamic means any type), is the same signature. This is because in ts, the Router interface extends RequestHandler

The overload that is used is

@:overload(function<P, ResBody, ReqBody, ReqQuery>(path:PathParams, handlers:haxe.extern.Rest<RequestHandler<P, ResBody, ReqBody, ReqQuery>>):Express { })

For logging, a pure haxe solution you might find handy one day is Console.hx which is something I put together so I could use terminal colors and formatting via a html syntax which I’m used to, e.g. Console.log('<b>Bold Text</b> and <red>Red Text</red>'). It’s got some additions to simplify the syntax too, the closing tag doesn’t need to repeat the opening tag name, so </> works, and tags can be merged together:
Console.log('<b,red>Bold and Red Text</>)

1 Like