ReScript RPC like?

Which solution comes to mind to when you control the client and the server.
You want to share types between front-end and back-end.

Right now I have a Hono server and I call this from my client using fetch.
And I cheat by saying “trust me bro” using

external fromJson: Js.Json.t => 't = "%identity"
external toJson: 't => Js.Json.t = "%identity"

// On the client
  let response = await fetch(
    url,
    {
      method: #POST,
      headers: Headers.fromObject({
        "Content-Type": "application/json",
      }),
      body: reqData
      ->toJson
      ->JSON.stringify
      ->Body.string,
    },
  )
  let json = await response->Response.json
  let resData: vrindelyckResponse = fromJson(json)

// On the server
  app
  ->Hono.post(route, async ctx => {
    let json = await ctx->Context.req->Request.json
    let requestData = json->fromJson

I mean, this all works, but do I really care how this is done on the http level?
The fact that my endpoint is a POST for example.
I just want to invoke something on the server, and get that return type.
Does anything come to mind to solve this?

1 Like

I believe rescript-rest with Fastify can do what you’re after: GitHub - DZakh/rescript-rest: 😴 ReScript RPC-like client, contract, and server implementation for a pure REST API

2 Likes

Yeah, I looked at that, but I still need to define the rest route and have some opinion on how I want to do it http-wise.

Maybe that is fine, but I just wonder if there is nothing out there where I can define the function server-side and invoke it on the client as if it was a local function.

You could also use a full server side framework like res-x or react server components.

1 Like

ReScript Rest is very flexible, and if you miss something, let me know about it in issues, and I’ll add it. For example, if your server uses Hono.js it’s not difficult to add an integration for it, so both client and server side are 100% typesafe.

As for the need to manually describe the contract, we are discussing with @zth the solution to codegen ReScript Rest from OpenAPI, but this will probably happen after ReScript v12 is released :sweat_smile:

2 Likes

Also, you can build a wrapper around it to always use JSON body for your transfer and simply use it like an RPC. Actually, I might make it myself as a part of the library :thinking:

Something like this:

// Contract.res
let getPost = Rest.rpc(() => {
  input: S.string,
  output: postSchema
})

// Client.res
let post = await Contract.getPost->Rest.fetch(url, "post_1")

// Server.res
app->Hono.route(Contract.getPost, async ({input: postId}) => {
    posts->Dict.getUnsafe(postId)
}

What do you think?

3 Likes

Yeah that would be pretty perfect!
I don’t need to care how Rest.fetch does the communication and it fits in nicely with Hono.

I don’t know how to make it without a separate Contract.res, but maybe it’s not bad. Also, you’ll always be able to move to Rest.route if you need more HTTP control.

Nice, I’ll add Rest.rpc today or tomorrow. And Hono.js integration a little bit later.

1 Like

Could you share the Hono.js bindings you use?

Loving all of this! Magic happening in real time.

4 Likes

I used rescript-hono/src/Hono.res at main · AlexMouton/rescript-hono · GitHub

(and in my project the additional

type middleware

@module("hono/cors")
external cors: unit => middleware = "cors"

@send
external use: (Hono.t<'t>, middleware) => unit = "use"

// To use 
app->use(cors())

)

I’m also not hell-bent on using Hono.
If HTTP server – API | Bun Docs is easier to integrate that would also work for me.

1 Like

Me neither. So I built an rpc library some time ago (wow, 3 years ago). Never published it on npm or somewhere else. Hasn’t been updated a while. Just for my own side project. But you can have a look: GitHub - dkirchhof/rescript-rpc

1 Like

Here it is

3 Likes

Thanks a lot!
So, for my understanding, I stil need to wire this on the server side like this right:

let app = Fastify.make()

app->Fastify.route(Contract.getPosts, async ({input}) => {
  // Implementation where return type is promise<'response>
})

let _ = app->Fastify.listen({port: 3000})

Yes. For now only Fastify and Next.js supported. And I’ll try to add Express and Hono.js support next week.

1 Like

Sorry, I was really busy recently. Contributions are welcome. I think it should be really similar to Fastify integration, which you can use as a reference.