Backend methods
When performing operations on multiple entity objects, performance considerations may necessitate running them on the server. With Remult, moving client-side logic to run on the server is a simple refactoring.
Set All Tasks as Un/completed
Let's add two buttons to the todo app: "Set all as completed" and "Set all as uncompleted".
Add a
setAllCompletedasync function to theTodofunction component, which accepts acompletedboolean argument and sets the value of thecompletedfield of all the tasks accordingly.ts// src/components/todo.tsx const setAllCompleted = async (completed: boolean) => { for (const task of await taskRepo.find()) { await taskRepo.save({ ...task, completed }) } }The
forloop iterates the array ofTaskobjects returned from the backend, and saves each task back to the backend with a modified value in thecompletedfield.Add the two buttons to the return section of the
Todocomponent, just before the closing</main>tag. Both of the buttons'onClickevents will call thesetAllCompletedmethod with the appropriate value of thecompletedargument.tsx// src/components/todo.tsx <div> <button onClick={() => setAllCompleted(true)}>Set All Completed</button> <button onClick={() => setAllCompleted(false)}>Set All Uncompleted</button> </div>
Make sure the buttons are working as expected before moving on to the next step.
Refactor from Front-end to Back-end
With the current state of the setAllCompleted function, each modified task being saved causes an API PUT request handled separately by the server. As the number of tasks in the todo list grows, this may become a performance issue.
A simple way to prevent this is to expose an API endpoint for setAllCompleted requests, and run the same logic on the server instead of the client.
- Create a new
TasksControllerclass, in thesharedfolder, and refactor theforloop from thesetAllCompletedfunction of theTodofunction component into a new,static,setAllCompletedmethod in theTasksControllerclass, which will run on the server.
// src/shared/TasksController.ts
import { BackendMethod, remult } from 'remult'
import { Task } from './Task'
export class TasksController {
@BackendMethod({ allowed: true })
static async setAllCompleted(completed: boolean) {
const taskRepo = remult.repo(Task)
for (const task of await taskRepo.find()) {
await taskRepo.save({ ...task, completed })
}
}
}The @BackendMethod decorator tells Remult to expose the method as an API endpoint (the allowed property will be discussed later on in this tutorial).
Unlike the front-end Remult object, the server implementation interacts directly with the database.
- Register
TasksControllerby adding it to thecontrollersarray of theoptionsobject passed tocreateRemultServer(), in the server'sapimodule:
// src/api.ts
//...
import { TasksController } from "./shared/TaskController"
export const api = remultApi({
//...
controllers: [TasksController]
})- Replace the
foriteration in thesetAllCompletedfunction of theTodocomponent with a call to thesetAllCompletedmethod in theTasksController.
// src/components/todo.tsx
const setAllCompleted = async (completed: boolean) => {
await TasksController.setAllCompleted(completed)
}Import TasksController
Remember to add an import of TasksController in src/components/todo.tsx.
Note
With Remult backend methods, argument types are compile-time checked. 👍
After the browser refreshed, the "Set all..." buttons function exactly the same, but much faster.