✅ Nact + Express example

nact

#1

Hi, does anyone have a Nact + Express example? I’m imagining that we’d wrap an Express server inside an actor, but I’m a bit fuzzy on how it would look. Maybe something like:

let express = Express.({
  let app = express();

  App.post(app, ...) /* Set up POST route */
  App.listen(app, ());
  app
});

let make(parent, messageHandlerActor) = Nact.spawn(
  ~name="Server",
  parent,
  (server, _, _) => {
    /* ... dispatch message to messageHandlerActor */
    Js.Promise.resolve(server)
  },
  express());

So I’m thinking that the server actor should capture the state of the Express server and be able to restart it if it crashes, and the above would achieve that. But I’m not sure, looking for some guidance.

Thanks


#2

OK I think I got it, the basic pattern is:

let express(messageHandler) = {
  open Express
  let app = express();

  App.get(app, ~path="/kill", Middleware.from((_, _, _) =>
    raise_notrace(Not_found)));

  App.post(app, ~path="...", PromiseMiddleware.from((_next, _req, res) =>
    Nact.query(
      ~timeout=100 * Nact.milliseconds,
      messageHandler,
      receiver => (receiver, /* ... parse a message out of the post body ... */))
      |> Js.Promise.(then_(result =>
        res |> Response.sendString(result) |> resolve))));

  Js.log("Starting HTTP server on port 3000");
  App.listen(app, ~port=3000, ());
  app
};

let make(parent, messageHandler): Nact.actorRef(unit) = Nact.spawn(
  ~name="HttpServer",
  parent,
  (express, _, _) => Js.Promise.resolve(express),
  express(messageHandler));

This spawns an actor that keeps a ‘live’ Express server as part of its state. The actor doesn’t care about handling any messages (although it could). It just exists to manage the Express server, which could crash. The server queries whichever handler actor it has a reference to when it gets HTTP requests, and sends back HTTP responses with the result of the query.

So for me right now this is working as planned, i.e. the server is getting restarted even when I send it the ‘kill’ HTTP request, and my other actors aren’t losing their state.

Nact is pretty cool! :smiley:


#3

Hi @yawaramin,

That’s a cool way of doing things! When combining express and nact, I typically separate the server from the actor system and use the query pattern to bridge the gap between the actor and non-actor systems. This isn’t for any particular reason except we have a fair amount of legacy code and frankly just hadn’t thought of a different approach.

For completeness, here is an example which takes this approach:

    open Nact.Operators;
    open Nact;

    let system = Nact.start();
    type sampleMessage =  | Factorial(int, Nact.actorRef(int));    
    let sampleActor = Nact.spawn(system, (Factorial(n, sender), _) =>  {
        let rec fac = (n, total) => switch(n) {
            | 0 => total
            | n => fac(n-1, n * total)
        };
        let result = fac(n, 1);
        sender <-< result;
    }); 

    open Express
    let app = express();
    App.get(app, ~path="/factorial/:number") 
        @@ PromiseMiddleware.from(
            (next, req, res) => {
                let params = Request.params(req);
                /* ...code to actually read the correct number from params */
                let n = 5;
                /* This is the nact query operator */
                sampleActor <? (temp => Factorial(n, temp), 5 * seconds)
                |> Js.Promise.then_(result => Response.sendString(Js.Int.toString(result), res))
            }    
        );

Another thought I had is that bs-express allows you to create new new types of middleware beyond just Middleware and PromiseMiddleware using the Middelware.Make functor. so an ActorMiddleware could be possible to build. For example an actor could be created to handle the lifetime of a request and would forward the req object another actor specified in the middleware, along with a reference to itself. If this request actor received a response in turn containing a Express.Response.t => complete, it could use this to resolve the request. (Really need to document bs-express more)


#4

Awesome, thanks Nick. I’ve been meaning to try Nact for a while and honestly didn’t think it would be this easy! You’ve documented it incredibly well. I’ll give you some more feedback soon.