• Blog
  • Talks

Next.js custom server with cron jobs

2023-07-25

(1 minute read)

For a turn-based game I'm working on I need to run a cron job at the server-side that processes all the moves placed by all players in order to calculate the results of the current round of play.

Since I love using Next.is for my backends this meant getting a custom server up and running. There isn't that much documentation around Custom servers but getting one up and running was fairly straightforward. Based on the official docs here is what I had for my server.ts (I'm using Typescript):

import next from 'next'
import { parse } from 'url'
import { createServer } from 'http'

import { startCron } from './cron'

const dev = process.env.NODE_ENV !== 'production'
const hostname = 'localhost'
const port = 3000
// when using middleware `hostname` and `port` must be provided below
const app = next({ dev, hostname, port })
const handle = app.getRequestHandler()

app.prepare().then(() => {
  startCron()

  const server = createServer(async (req: any, res: any) => {
    try {
      // Be sure to pass `true` as the second argument to `url.parse`.
      // This tells it to parse the query portion of the URL.
      const parsedUrl = parse(req.url, true)
      await handle(req, res, parsedUrl)
    } catch (err) {
      console.error('Error occurred handling', req.url, err)
      res.statusCode = 500
      res.end('Internal server error')
    }
  })
    .once('error', (err: any) => {
      console.error(err)
      process.exit(1)
    })
    .listen(port, () => {
      console.log(`> Ready on http://${hostname}:${port}`)
    })
})

This is quite straightforward. The cron service is started just before we start the HTTP server.

Inside cron.ts we setup a new cron-async instance and feed it the jobs one by one:

import { Cron } from 'cron-async'

import * as cronJobOne from './cronJobOne'
import * as cronJobTwo from './cronJobTwo'

export const startCron = () => {
  const cron = new Cron()

  ;[
    cronJobOne,
        cronJobTwo,
  ].forEach(jobSpec => {
    cron.createJob(jobSpec.name, {
      cron: jobSpec.schedule,
      onTick: async () => {
        await jobSpec.run(ctx)
      }
    })
  })

    console.info('Cron started')
}

In my case each cron job is in its own module and exports a name, cron schedule and run() method. For example, here is what cronJobOne.ts would look like :

export const name = 'cron job 1'

export const schedule = '* * * * * *' // every second

export const run = async () => {
    console.log('job running')
    
    // TODO: other stuff in here
}

Note that the run() method is async, meaning you can execute long-running tasks within it. And the cron scheduler is smart enough to wait for the current call to run() to finish executing prior to trigger the next run, even if the cron schedule is set to run every 1 second.

And that's all there is to it. If you run the Next.js server you should start seeing the cron job getting executed as defined above.

  • Home
  • Blog
  • Talks
  • Github
  • Linked-in
  • Email
  • RSS
© Hiddentao Ltd