In a vanilla Elm application, ports let you either send or receive JSON data between your Elm application and the JavaScript context in the user's browser at runtime.
With BackendTask.Custom, you send and receive JSON to JavaScript running in NodeJS. As with any BackendTask, Custom BackendTask's are either run at build-time (for pre-rendered routes) or at request-time (for server-rendered routes). See BackendTask for more about the
lifecycle of BackendTask's.
This means that you can call shell scripts, run NPM packages that are installed, or anything else you could do with NodeJS to perform custom side-effects, get some data, or both.
A BackendTask.Custom will call an async JavaScript function with the given name from the definition in a file called custom-backend-task.js in your project's root directory. The function receives the input JSON value, and the Decoder is used to decode the return value of the async function.
Here is the Elm code and corresponding JavaScript definition for getting an environment variable (or an FatalError BackendTask.Custom.Error if it isn't found). In this example,
we're using BackendTask.allowFatal to let the framework treat that as an unexpected exception, but we could also handle the possible failures of the FatalError (see FatalError).
import BackendTask exposing (BackendTask)
import BackendTask.Custom
import Json.Encode
import OptimizedDecoder as Decode
data : BackendTask FatalError String
data =
BackendTask.Custom.run "environmentVariable"
(Json.Encode.string "EDITOR")
Decode.string
|> BackendTask.allowFatal
-- will resolve to "VIM" if you run `EDITOR=vim elm-pages dev`// custom-backend-task.js
module.exports =
/**
* @param { unknown } fromElm
* @returns { Promise<unknown> }
*/
{
environmentVariable: async function (name) {
const result = process.env[name];
if (result) {
return result;
} else {
throw `No environment variable called ${name}
Available:
${Object.keys(process.env).join("\n")}
`;
}
},
}As with any JavaScript or NodeJS code, avoid doing blocking IO operations. For example, avoid using fs.readFileSync, because blocking IO can slow down your elm-pages builds and dev server. elm-pages performances all BackendTask's in parallel whenever possible.
So if you do BackendTask.map2 Tuple.pair myHttpBackendTask myCustomBackendTask, it will resolve those two in parallel. NodeJS performs best when you take advantage of its ability to do non-blocking I/O (file reads, HTTP requests, etc.). If you use BackendTask.andThen,
it will need to resolve them in sequence rather than in parallel, but it's still best to avoid blocking IO operations in your Custom BackendTask definitions.
There are a few different things that can go wrong when running a custom-backend-task. These possible errors are captured in the BackendTask.Custom.Error type.
Any time you throw a JavaScript exception from a BackendTask.Custom definition, it will give you a CustomBackendTaskException. It's usually easier to add a try/catch in your JavaScript code in custom-backend-task.js
to handle possible errors, but you can throw a JSON value and handle it in Elm in the CustomBackendTaskException call error.