Backend only code
One of the main advantages of remult is that you write code once, and it runs both on the server and in the browser.
However, if you are using a library that only works on the server, the fact that the same code is bundled to the frontend can cause problems. For example, when you build an Angular project, you'll get Module not found
errors.
This article will walk through such a scenario and how it can be solved.
For this example, our customer would like us to document each call to the updatePriceOnBackend
method in a log file.
Our first instinct would be to add in the products.component.ts
file an import to fs
(Node JS file system component) and write the following code:
import * as fs from 'fs';
.....
@BackendMethod({allowed:true})
static async updatePriceOnBackend(priceToUpdate:number,remult?:Remult){
let products = await remult.repo(Products).find();
for (const p of products) {
p.price.value += priceToUpdate;
await p.save();
}
fs.appendFileSync('./logs/log.txt', new Date() + " " + remult.user.name + " update price\n");
}
Error
As soon as we do that, we'll get the following errors on the ng-serve
terminal
ERROR in ./src/app/products/products.component.ts
Module not found: Error: Can't resolve 'fs' in 'C:\try\test19\my-project\src\app\products'
i 「wdm」: Failed to compile.
We get this error because the fs
module on which we rely here is only relevant in the remult of a Node JS
server and not in the context of the browser.
There are two ways to handle this:
Solution 1 - exclude from bundler
Step 1 - replace the File level import with an import in the specific backend method:
// import * as fs from 'fs'; <-- REMOVE THIS LINE
.....
@BackendMethod({allowed:true})
static async updatePriceOnBackend(priceToUpdate:number,remult?:Remult){
const fs = await import('fs');
let products = await remult.repo(Products).find();
for (const p of products) {
p.price.value += priceToUpdate;
await p.save();
}
fs.appendFileSync('./logs/log.txt', new Date() + " " + remult.user.name + " update price\n");
}
Step 2 - Exclude the package from the bundler:
If you're using
vite
instructvite
not to include thefs
package in thefrontend
bundle by adding the following JSON to thevite-config.ts
file:tsimport { defineConfig } from "vite" import react from "@vitejs/plugin-react" // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], build: { rollupOptions: { external: ["fs", "nodemailer", "node-fetch"], }, }, })
If you're using
webpack
(create-react-app or Angular) Instructwebpack
not to include thefs
package in thefrontend
bundle by adding the following JSON to the main section of the project'spackage.json
file. package.jsonjson"browser": { "jsonwebtoken": false }
- note that you'll need to restart the react/angular dev server.
Solution 2 - abstract the call
Abstract the call and separate it to backend only files and inject
it only when we are running on the server.
Step 1, abstract the call - We'll remove the import to fs,
and instead of calling specific fs
methods, we'll define and call a method writeToLog
that describes what we are trying to do:
//import * as fs from 'fs';
.....
@BackendMethod({allowed:true})
static async updatePriceOnBackend(priceToUpdate:number,remult?:Remult){
let products = await remult.repo(Products).find();
for (const p of products) {
p.price.value += priceToUpdate;
await p.save();
}
//fs.appendFileSync('./logs/log.txt', new Date() + " " + remult.user.name + " update price\n");
ProductsComponent.writeToLog(new Date() + " " + remult.user.name + " update price\n");
}
static writeToLog:(textToWrite:string)=>void;
The method writeToLog
that we've defined serves as a place holder which we'll assign to in the remult of the server. It receives one parameter of type string
and returns void
.
Step 2, implement the method: In the /src/app/server
folder, we'll add a file called log-writer.ts
with the following code:
import * as fs from 'fs';
import { ProductsComponent } from '../products/products.component';
ProductsComponent.writeToLog = what => fs.appendFileSync('./logs/log.txt', what);
Here we set the implementation of the writeToLog
method with the actual call to the fs
module. This file is intended to only run on the server, so it'll not present us with any problem.
Step 3, load the log-writer.ts
file: In the /src/app/server/server-init.ts
file, load the log-writer.ts
file using an import
statement
import '../app.module';
import './log-writer'; //load the log-writer.ts file
import { Pool } from 'pg';
import { config } from 'dotenv';
import { PostgresDataProvider, PostgresSchemaBuilder } from '@remult/server-postgres';
import * as passwordHash from 'password-hash';
That's it - it'll work now.
TIP
If you're still getting an error - check that you have a logs
folder on your project 😃