Why is this rescript object 'undefined' and how to fix this code?

Hi,

I’m following the converting-from-js tutorial (Converting from JS | ReScript Language Manual) on a very simple example of code, but, when converting that javascript instruction (which is working well) :
res.setHeader('Content-Type', 'text/plain')
using the object feature as suggested in the tutorial:
res["setHeader"]("Content-Type", "text/plain")
I get the following error:

TypeError: Cannot read properties of undefined (reading '_header')
at setHeader (node:_http_outgoing:643:12)
at Object._2 (/home/app/node_modules/rescript/lib/js/curry.js:81:12)

Is it because the setHeader function is actually a setter and object feature does not support it ?

Then how should I convert that js code to rescript ?

I’d need to see more of your code to be sure, but the error message states, the object itself is undefined. Therefore, there can’t be any prop accessible.
It seems res is undefined.

Where does this value come from?
Are you binding to js? Could you post your bindings as well? Or more of your code in general?
Maybe you forgot to use/bind a constructor? (Like in js: new SomeClass())


Edit: I see step 5 of the converting from js tutotial as absolutely desirable to build idiomatic api surfaces.

I’d go even further and introduce a submodule . Like a common convention:

module School = {
  type t
 
  module Student = {
    type t
    // functions specifically working on a student, would go here
  }

  @module external make: t = "school" // note: is school really already accessible at this point, or do we need to construct it?
  @send external getStudentById: (t, int) => Student.t = "getStudentById"
}


type payload = {
  student: student
}

let defaultId = 10

let queryResult = (usePayload, payload) => {
  if usePayload {
    payload.student
  } else {
    School.(make->getStudentById(defaultId))
  }
}

It’s not unlikely that setHeader is expected to be called as a method that is trying to access this._header. One funny thing about JavaScript that whether or not this will be bound when evaluating a function depends (in most cases) on how it’s called, not how it’s defined. In other words, in JavaScript, res.setHeader(...) will bind this to res, but var f = res.setHeader; f(...) will not.

And unfortunately, in rescript, res["setHeader"](...) is more like the second form. It will not bind this to res, and therefore the function will fail if it tries to access properties on this. It used to be that this did generate a method call, but that changed in 9.1 with the object representation overhaul. Not sure if it was intentional, but it sure does make this guide rather misleading.

If you keep going and do (the not so optional) step 5 as well, it should work. That’ll also make your code much more robust and idiomatic.

2 Likes

Then I’m going to try step 5. I would say that my example is more like the payload.student example. The question is: how do I know what to put in that payload type. Or in my example, what sort of type should I declare for res ?

Here is the code, if that can help :

%%raw("
const express = require('express')
const app = express()

const postCompile = (req, res, next) => {
  res.setHeader('Content-Type', 'text/plain');
  res.write('Ok');
  res.end(undefined);
};

app.get('/', healthCheck)
app.post('/compile/', postCompile)


module.exports = () => {
  app.listen(port, () => {
    console.log(`Listening at http://localhost:${port}`)
  })
};

")

let port = 3000

let healthCheck = (req, res, next) => {
  res["setHeader"]("Content-Type", "text/plain")
  res["write"]("Ok")
  res["end"](None)
};

You can add a dot in front of the first argument in each function call. Like:

res["end"](. None) 

This way it will work as you expect.

The dot makes the function uncurried. It should become the default behaviour in the rescript@11, really looking for the moment.

4 Likes

I created a first draft of how I would write it in the playground:

// LIB / BINDINGS
module Error = {
  @new
  external make: string => Js.Exn.t = "Error"
}

module Express = {
  module Request = {
    type t
  }

  module Response = {
    type t

    @send
    external set: (t, ~name: string, ~value: string) => unit = "set"

    @send
    external setMany: (t, Js.Dict.t<string>) => unit = "set"

    @send
    external status: (t, int) => t = "status"

    @send
    external send: (t, string) => unit = "send" // NOTE: this actually takes other types than string as well, according to https://expressjs.com/en/5x/api.html#res.send

    @send
    external json: (t, Js.Json.t) => unit = "json"

    module NodeResponse = {
      @send
      external setHeader: (t, ~name: string, ~value: string) => unit =
        "setHeader" // NOTE: this really returns t, but it's easier to use this way

      @send
      external write: (t, string) => unit = "write" // NOTE: this really returns bool, but it's easier to use this way

      @send
      external end: t => unit = "end"
    }
  }

  module Application = {
    type t

    @module @new
    external make: unit => t = "express"

    type next = (~error: Js.Exn.t=?) => unit
    type callback = (Request.t, Response.t, next) => unit

    @send
    external get: (t, string, callback) => unit = "get"

    @send
    external post: (t, string, callback) => unit = "post"

    @send
    external listen: (t, int, unit => unit) => unit = "listen"
  }
}

// APPLICATION
open Express.Application

let app = make()

let okText = (_req, res, _next) => {
  open Express.Response
  res->set(~name="Content-Type", ~value="text/plain")
  res->send("Ok")
}

let error = (isUnhandled, _req, res, next: next) => {
  open Express.Response
  if isUnhandled {
    next(~error=Error.make("Server Error"))
  } else {
    res->set(~name="Content-Type", ~value="text/plain")
    res->status(500)->send("Server Error")
  }
}

app->get("/", okText)
app->post("/compile/", okText)
app->get("/error/handled", error(false))
app->get("/error/unhandled", error(true))

let port = 8080

let default = () => {
  app->listen(port, () =>
    Js.Console.log("Listening at http://localhost:" ++ port->Js.Int.toString)
  )
}
default()

Warning: I’m not using Express regularly, therefore my usage may not be optimal. But I’m very confident to write bindings. I tried to stick to the expressjs docs.
I successfully ran the given code, but didn’t test every possible aspect.
My example code does not translate exactly to your given raw-block, but uses functions provided by express and not the underlying node apis. (I still included them in the bindings)

@bloodyowl created a package of bindings for express a while ago as well. I’m not sure on how up to date the package actually is. But I’m sure there is a lot you can learn from that repo as well, since the author is well experienced in using rescript.

Do you have some questions about the given example?

The express api is incredibly stable and I’ve used this package recently with success.

1 Like

Ok, thanks a lot for your answers.

@DZakh it works indeed, I think that’s what I was looking for, since I was expecting that code to work without any need for “hard” bindings.

Nevertheless, thanks @woeps for your draft, I will definitely use this eventually.

I already tried to use the @bloodyowl package and I must admit that I didn’t dig so much, but it seemed to me that for my very simple example, I should not need such complete bindings. I might be wrong.

I want to just use the js library from rescript, to prove that we can easily integrate with some js code, even if, at first, it means losing the “type security” of rescript. I might be wrong.

1 Like

@bloodyowl Updated the documentation in the https://github.com/rescript-association/rescript-lang.org/pull/612, so the tutorial code works as intended now :raised_hands:

1 Like