DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on • Updated on

Electron Adventures: Episode 3: What Can Backend Code Even Do?

Let's do something that frontend code can't do on its own - run shell commands.

Run a single command

Electron on the backend is basically node process, with some extras for communicating with the browser.

Now it would much prefer you to use complicated async system with proper error handling and so on, but we don't need this complexity - we'll just use child_process.execSync to run a command and capture its output.

We need to do two small things with the result. First we need to convert Buffer into String - this isn't done automatically, as the output could be some binary like an image, not a valid UTF-8 String. And then we'll trim extra newline.

let child_process = require("child_process")

let runCommand = (command) => {
  return child_process.execSync(command).toString().trim()
}
Enter fullscreen mode Exit fullscreen mode

Gather information about operating system

Let's run a bunch of random commands to get system information

let sysInfo = {
  os: runCommand("uname -s"),
  cpu: runCommand("uname -m"),
  hostname: runCommand("hostname -s"),
  ip: runCommand("ipconfig getifaddr en0"),
}
Enter fullscreen mode Exit fullscreen mode

Ship this information to the frontend

We can now ship this information to the frontend. There are many more complex ways, and we'll absolutely get there, but for now, let's jut use the simplest one and pass a query string.

Weirdly Javascript still doesn't have a way to turn an Object into a query string, so we'll need to roll our own!

let toQueryString = (obj) => {
  let q = []
  for (let key in obj) {
    q.push(`${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
  }
  return q.join("&")
}
Enter fullscreen mode Exit fullscreen mode

We used loadFile function before, but that doesn't have any easy way of passing an query string. But we can do a little trick, and use loadURL with file: protocol instead.

let { app, BrowserWindow } = require("electron")

function createWindow() {
  let win = new BrowserWindow({})
  win.maximize()
  win.loadURL(`file:${__dirname}/index.html?${toQueryString(sysInfo)}`)
}

app.on("ready", createWindow)

app.on("window-all-closed", () => {
  app.quit()
})
Enter fullscreen mode Exit fullscreen mode

Parse this on the frontend

Javascript has a way to parse a query string, but it's not very convenient. But let's give it a go the hard way with just browser APIs:

let data = new URLSearchParams(document.location.search)

let info = document.querySelector("#info")

for (const [key, value] of data) {
  let p = document.createElement("p")
  p.append(`${key} = ${value}`)
  info.append(p)
}
Enter fullscreen mode Exit fullscreen mode

We absolutely do not want to be writing bigger apps this way of course, so we'll get to using libraries soon.

For now let's just add this simple HTML, and we have a simple app:

<!DOCTYPE html>
<html>
  <body>
    <h1>System information!</h1>
    <div id="info"></div>
    <script src="app.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The result

And here's what we got:

Alt Text

All the code for the episode is here.

See you in the next episode!

Top comments (2)

Collapse
 
darrylnoakes profile image
Darryl Noakes • Edited

RE: Query strings

You can use URLSearchParams.

let toQueryString = (params) => {
  return new URLSearchParams(params).toString();
}
Enter fullscreen mode Exit fullscreen mode
function appendSearchParams(
  url: string,
  params?: Record<string, string>
) {
  return url + "?" + toQueryString(params);
}
Enter fullscreen mode Exit fullscreen mode

I have this for one of my current projects:

function resolve(
  url: string | URL
  params?: Record<string, string>
) {
  const _url = new URL(url);
  new URLSearchParams(params).forEach((value, key) => {
    _url.searchParams.append(key, value);
  });
  return _url.toString();
}
Enter fullscreen mode Exit fullscreen mode

I use this because there is special handling by the URLSearchParams objects (e.g. the question mark in the URL). Also, it allows other operations on the Params and URL if necessary.

You could loop over the param object's entries manually, instead creating a URLSearchParams object and using forEach.
Using it seems more foolproof, so it's probably a good idea unless you need high performance.
Code:

function resolve(
  url: string | URL
  params?: Record<string, string>
) {
  const _url = new URL(url);
  if (params) {
    for (
      const [key, value] of Object.entries(params)
    ) {
      _url.searchParams.append(key, value);
    }
  }
  return _url.toString();
}
Enter fullscreen mode Exit fullscreen mode

The check on params is needed in because it is an optional parameter. I use a fuller form of this function for an internal API library, so a query string is not always required.
In JS, this check should be done anyway, in theory. In TS, simply make it a mandatory parameter.

Note: URL.searchParams is a read only instance of URLSearchParams.

MDN Docs:
URL
URLSearchParams

Collapse
 
taw profile image
Tomasz Wegrzanowski

Thanks, I didn't know about this.