ReScript + React with Thin Backend

Learn how to add Thin Backend to your ReScript application

1. Introduction

This guide covers how to use Thin as the Backend for your ReScript application. This Guide assumes you're using React.js for your view layer.

2. Install Packages

New Project

If you don't have a ReScript project yet, it's easiest to start out with the ReScript + Thin Backend template:

# Clone the template
npx degit digitallyinduced/ihp-backend-rescript-starter my-rescript-app

# Switch to template dir
cd my-rescript-app

# Install deps
npm install

After generating the project, we need to manually configure the BACKEND_URL to your project's url. For that create a new file .env and put this into it:

BACKEND_URL=https://<YOUR PROJECTS BACKEND_URL>

Existing ReScript Project

You can also add Thin Backend to your existing ReScript project:

  1. First install the ihp-backend package:

    npm install thin-backend
    
  2. Change your react entrypoint to call IHPBackend.init:

    // Entry point of the application
    
    // This needs to be called before `ReactDOM.render` is called
    IHPBackend.init({ host: "https://<YOUR PROJECTS BACKEND_URL>" })
    
    switch(ReactDOM.querySelector("#app")){
    | Some(root) => ReactDOM.render(<App />, root)
    | None => () // do nothing
    }
    
  3. Wrap your app's main component with the <ThinBackend> component:

    open IHPBackend
    
    @react.component
    let make = () => {
        <IHPBackend>
            <MyAppContent />
        </IHPBackend>
    }
    

  4. Add the rescript-ihp-backend package in bs-dependencies of your project's bsconfig.json:

    // bsconfig.json
    
    {
      // ..
      "bs-dependencies": [
        "@rescript/react",
        "rescript-ihp-backend" // <-- ADD THIS
      ],
    }
    

    The rescript-ihp-backend dependency provides the type signatures for the ihp-backend backend. We will install this rescript-ihp-backend package in a few moments. For now ignore any compiler warnings.

3. Installing Types

Thin Backend generates ReScript type definitions based on your database schema. The type definitions can be installed into a project by installing a project-specific rescript-ihp-backend npm package. This npm package is specifically generated for your project and tagged with a secret url, so only you can access it.

To install your project's Types, open the Schema Designer. Click the Type Definitions tab. Now you see a npm install .. command:

Run this command locally in your project directory. After that the types will be automatically available in your project.

When you make changes to the database schema, a new type definition package will be generated. To use the latest types, open the Type Definitions tab again and install the latest package listed there.

4. Starting

Now that the types are installed, your app should be in a runnable state again.

If you've used the ReScript project template, use these commands to start your local dev server:

# Start compiler
npm run rescript:dev

# Start server, Run this in a second terminal window
npm run dev

5. Retrieving the Current User

A good starting point is to display the current logged in user and a logout button in your app. You can use useCurrentUser() for this.

  1. Create a new file src/UserStatus.res:

    open IHPBackend
    
    @react.component
    let make = () => {
        let user = useCurrentUser()
        let isLoggedIn = useIsLoggedIn()
    
        let onLogoutClick = _ => {
            let _ = logout()
        }
        
        let onLoginClick = _ => {
            let _ = loginWithRedirect()
        }
    
        switch isLoggedIn {
        | Some(false) => <div>
                <button onClick={onLoginClick}>{React.string("Login")}</button>
            </div>
        | Some(true) => switch user {
            | Some(user) => <div>
                    {React.string(user.email)}
                    <button onClick={onLogoutClick}>{React.string("Logout")}</button>
                </div>
            | None => <div>{React.string("Loading")}</div>
            }
        | None => <div>{React.string("Loading")}</div>
        }
    }
    

    The logout function used in the onClick is provided by Thin Backend. It deletes the locally stored JWT for the current user session and then redirects the user back to the login page.

  2. Call the new UserStatus component from the App.res component:

    open IHPBackend
    
    @react.component
    let make = () => {
        <IHPBackend>
            <UserStatus/>
        </IHPBackend>
    }
    
  3. Now your app should show a login button, or your email address and a logout button. Go try it out :)

6. Fetching Data

You can use the useQuery hook to access your project's database. Let's say your building a todo list app and you've previously created a tasks table in the Schema Designer. The following code will show all tasks:

// src/Tasks.res
open IHPBackend
open IHPBackend.Task

@react.component
let make = () => {
    let tasks = useQuery(queryTasks->orderByCreatedAtDesc)

    switch tasks {
    | Some(tasks) => Belt.Array.map(tasks, task => <Task key={task.id} task/>)->React.array
    | None => React.string("loading")
    }
}

The useQuery(query('todos')) call in our TodoList sets up a subscription behind the scences. Whenever the result set of our query('todos') we fired here changes, it will trigger a re-render of our component.

You also need to create a new Task component at src/Task.res:

// src/Task.res
open IHPBackend

@react.component
let make = (~task: task) => {
    <div>{React.string(task.title)}</div>
}

The task type is provided by Thin Backend and has all fields of your tasks table.

7. Writing Data

You can call createSomething(someObject) insert something into your database:

// src/NewTaskButton.res
open IHPBackend
open IHPBackend.Task

@react.component
let make = () => {
    let addTask = _ => {
        let _ = createTask({
            title: %raw("window.prompt('Enter title')")
        })
    }
    <button onClick={addTask}>{React.string("Add Task")}</button>
}

Don't forget to call this new component from your main component, otherwise it will not show up.

8. Next Steps

Check out the Database Guide for a full list of all database operations you can do with Thin.

Next: Database Guide

Most of the examples in the docs use JavaScript. Some functions are called a little bit different form ReScript than from JavaScript. Here's a comparison table that might be helpful:

JavaScriptReScript
query("tasks")
    .orderBy("createdAt")
    .filterWhere("projectId", someProjectId)
    .limit(50)
queryTasks
    ->orderByCreatedAt
    ->whereProjectId(someProjectId)
    ->limit(50)
await createRecord('tasks', { title: 'Hello World!' })
createTask({ title: "Hello World!" })
await updateRecord('tasks', task.id, { title: 'New title!' })
updateTask(task.id, { title: "New title!" })
await deleteRecord('tasks', task.id)
deleteTask(task.id)

If you check out the compiled JS output of the rescript compiler, you will see that ReScript calls are transformed to the above JS calls.

Community

If you need any help or input, feel free to ask in the Thin Community Forum.