Core boiler plate code to run node application as a service in both windows or linux with auto-update.
  • JavaScript 100%
Find a file
Jonatan Nilsson 2c9bc7974c
All checks were successful
/ deploy (push) Successful in 7s
Fix deprecation warning
2026-03-30 19:51:29 +00:00
.forgejo/workflows major: Remove lowdb, use minimal local fork copy 2026-03-30 19:31:20 +00:00
bin test: Fix keepAlive and 7zdec tests 2025-08-09 01:39:14 +00:00
core Fix deprecation warning 2026-03-30 19:51:29 +00:00
test bunyan: Add missing cli parser for pretty output 2026-03-30 19:43:23 +00:00
.gitignore Some cleanup on files. Remove skip tests 2022-02-18 08:16:36 +00:00
.npmrc Mode development. Boilerplate finished 2020-09-01 17:31:38 +00:00
appveyor.yml Application: Expose some helper classes to application through ctx.sc 2022-03-27 15:12:04 +00:00
cli.mjs cli: Add basic install support for linux 2022-03-13 01:22:45 +00:00
exampleconfig.json Finished basic beta implementation of entire thing. 2022-02-15 11:28:30 +00:00
index.mjs Fix deprecation warning 2026-03-30 19:51:29 +00:00
package.json bunyan: Add missing cli parser for pretty output 2026-03-30 19:43:23 +00:00
README.md major: Remove lowdb, use minimal local fork copy 2026-03-30 19:31:20 +00:00

service-core

Service-Core is a project to faciliate running a node application in production environment on a windows or linux machine. Using Windows Services, Service-Core will register itself and autostart on startup and make sure the application is running. You can also run it as a systemd service in Linux to get the same features. One of the main features of service-core is it being able to take care of maintaining the application including auto updating it seamlessly.

How to use for development

Each application published and pulled from github-like releases needs to have a file index.mjs that contains an exported function called start(). Here's a basic boiler plate code for index.mjs

index.mjs

import fs from 'fs'
import { pathToFileURL } from 'url'

export function start(http, port, ctx) {
  // Load our main server and start it
  return import('./server.mjs')
  .then(function(module) {
    let server = new module.default(http, port, ctx)
    return server.run()
  })
}

// Allows us to run this file directly
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  import('service-core').then(core => {
    const port = 5000

    var core = new core.ServiceCore('name-of-project-here', import.meta.url, port, '')

    // Default dev runtime config
    let config = {
      NODE_ENV: 'development',
      some_config_variable: 'exposed here',
    }
    let extra = {}

    // Optional allow developer to have their own overwrite
    if (fs.existsSync('./config.json')) {
      extra = JSON.parse(fs.readFileSync('./config.json'))
    }

    config = {
      ...config,
      ...extra,
    }

    config.port = port

    core.setConfig(config)
    core.init({ start }).then(function() {
      return core.run()
    })
  })
}

server.mjs

import { Flaska } from 'flaska'

export default class Server {
  constructor(http, port, core) {
    this.http = http
    this.port = port
    this.core = core

    this.config = Object.assign({}, core.config)

    // Use the logger provided by service-core
    this.flaskaOptions = {
      log: this.core.log,
    }
  }

  run() {
    // Create our server
    this.flaska = new Flaska(this.flaskaOptions, this.http)

    // configure our server
    if (this.config.NODE_ENV === 'development') {
      this.flaska.devMode()
    }

    this.flaska.get('/', async (ctx) => {
      ctx.body = { version: this.core.app.ctx.version }
    })
    
    return this.flaska.listenAsync(this.port).then(() => {
      this.core.log.info('Server is listening on port ' + this.port)
    })
  }
}

How to run as a service

Create a runner file of your choice, ex. runner.mjs with something like this:

import fs from 'fs'
import { runner } from 'service-core'

runner(import.meta.url, 'config.json', 'db.json')
.then(
  function(core) {
    core.log.info('core is running')
  },
  function(err) {
    runner.log.error(err, 'Error starting runner')
    process.exit(1)
  }
)

As well as a config file specified above, ex config.json for service-core, something like this for example:

{
  "name": "cb",
  "cb": {
    "http2": true,
    "provider": "git",
    "url": "https://git.nfp.is/api/v1/repos/thething/compress_benchmark/releases",
    "cluster": 1,
    "port": 3880,
    "scAllowStop": true,
    "NODE_ENV": "production"
  },
  "manager": {
    "provider": "git",
    "url": "https://git.nfp.is/api/v1/repos/thething/sc-manager/releases",
    "port": 4880,
    "scAllowStop": false
  }
}

Then all you have to do is call node runner.mjs et voila. It will boot up service-core, fetch latest releases from the specified url's and being running, and maintaining it.

Example systemd service file you can use: /etc/systemd/system/myname.service

[Unit]
Description=Service $1
After=network.target
StartLimitIntervalSec=0

[Service]
Type=simple
Restart=always
RestartSec=1
User=$1
ExecStart=node /home/$1/runner.mjs
WorkingDirectory=/home/$1
StandardOutput=append:/var/log/$1/stdout.log
StandardError=append:/var/log/$1/stderr.log

[Install]
WantedBy=multi-user.target

The Core

The core provides methods for updating applications as well as taking care of restarting and installing and everything needed to have a pleasent experience running a node application in Windows. It auto checks github for new releases based on the repository specified in config.json.

The core supports running two applications by default (specified in config.json file):

  • The manage app: Designated UI node app to provide UI interface on top of service-core. Not needed as service-core already does everything by itself but nice to have to remotely read logs and manually trigger updates among other things
  • The main app: The main application service-core is designated to run.

Both the main app and manage app get regular update checks and will automatically be installed if a new version is detected.

API

To build a service-core application I recomennd checking out hello world app but in short, all service core applications require the following things:

  • index.mjs that exposes a function called start(config, db, log, core, http, port)
  • The application in question must use the passed on http parameter to call .createServer(). Otherwise service-core has no way of shutting it down to provide seamless updates among other things.

The start() function gets called with following parameters:

  • config: JSON object containing the entirety of config.json
  • db: A minimal fork of lowdb database available for the application to use. Also used internally in service-core to manage versions.
  • log: A bunyan logger for logging.
    • log.event.info,warn,error(message): Write a log message to the windows event viewer.
  • core: The internal core. Exposes multiple methods for managing service-core
  • http: A wrapped internal node http to call .createServer(). Allows service-core to monitor the server in question.
  • port: The port the application should be listening to.