PingArkDocsJavaScript SDK
Guide

PingArk for JavaScript and TypeScript

If you run scheduled jobs in JavaScript or TypeScript, the pingark package is the quickest way to monitor them. Wrap a job in one function and PingArk knows when it starts, when it finishes, how long it took, and what went wrong if it failed. You never touch a ping URL by hand.

It is framework-agnostic. The core works in any modern runtime with fetch (Node 18 and newer, Bun, Deno, and edge functions), so it fits Next.js, Nuxt, Express, Remix, SvelteKit, or plain Node, Bun, and Deno. Thin adapters for Next.js, Express, and Nuxt are included for the common cases. The package is open source and lives on GitHub and npm. Under the hood it just speaks the same ping API any job can use, so nothing here is magic.

Requirements

  • Any runtime with a global fetch: Node 18 or newer, Bun, Deno, or an edge runtime
  • A PingArk project, and its ping key

Scheduled-task monitoring is a server-side concern, so there is no browser build. For the Vue ecosystem, use the Nuxt and Nitro adapter on the server side.

Install

Add the package with your package manager of choice.

npm install pingark
Copy

It ships both ESM and CommonJS builds with TypeScript types, so import and require both work with no extra setup.

Configure

Set your project ping key in the environment. You will find it in PingArk under your project's settings. That single value is all most applications need.

PINGARK_PING_KEY=your-project-ping-key

# Optional. Only needed for the management API client.
PINGARK_API_KEY=your-read-write-api-key
Copy

The top-level helpers read these variables automatically. If you would rather pass options in code, or run more than one project from the same process, create a client instead.

import { createPingArk } from 'pingark'

const pingark = createPingArk({
  pingKey: 'your-project-ping-key',
})
Copy

The ping key is a capability secret. Anyone who has it can ping your checks, so keep it in your environment and out of version control, exactly like any other credential. The full list of options is in the config reference below.

The one-liner

Wrap a job in monitor. That is the whole integration.

import { monitor } from 'pingark'

await monitor(() => runNightlyJob(), 'nightly-job')
Copy

On each run, monitor sends a start ping before your function, a success ping after it resolves, and a failure ping if it throws. PingArk measures the run duration for you from the start and finish. The value your function returns is passed straight through, and if it throws, the original error is re-thrown after the failure ping is sent, so wrapping a job never changes its outcome. It only reports it.

The second argument is the check slug, which must match a check in PingArk. Create it in the dashboard, or let the first ping create it.

Sending signals

Sometimes you want to signal PingArk by hand rather than wrap a whole function. Each ping signal has its own helper.

import { start, success, fail } from 'pingark'

await start('nightly-job')   // records a start time so duration can be measured
await success('nightly-job') // job finished, re-arms the check
await fail('nightly-job')    // job failed, sends the check down
Copy

These top-level helpers use a client configured from the environment. When you have made a client with createPingArk, call the same methods on it.

import { createPingArk } from 'pingark'

const pingark = createPingArk()

await pingark.start('nightly-job')
await pingark.success('nightly-job')
Copy

If you set PINGARK_DEFAULT_CHECK (or pass defaultCheck to createPingArk), you can call these with no check argument, which is handy in an app built around a single job.

Next.js and Vercel Cron

For a Vercel Cron job, wrap your route handler with withPingArk from the pingark/next entry. It pings around the handler and returns the handler's own response.

// app/api/cron/nightly/route.ts
import { withPingArk } from 'pingark/next'

export const GET = withPingArk(
  async () => {
    await runNightlyJob()
    return Response.json({ ok: true })
  },
  { check: 'nightly-job' },
)
Copy

Then point a cron at that route in vercel.json, exactly as you would without PingArk.

{
  "crons": [{ "path": "/api/cron/nightly", "schedule": "0 2 * * *" }]
}

If the handler throws, the wrapper sends a failure ping with the error and re-throws, so your route's error handling is unchanged.

Express

For an Express server, add the middleware from pingark/express to the route your scheduler hits. It pings start when the request arrives, then success on a normal finish, or failure on a 5xx response.

import express from 'express'
import { pingarkExpress } from 'pingark/express'

const app = express()

app.get('/cron/nightly', pingarkExpress('nightly-job'), (req, res) => {
  runNightlyJob()
  res.send('ok')
})
Copy

Nuxt and Nitro

For a Nitro scheduled task (the server side of Nuxt), wrap the task's run function with withPingArk from pingark/nuxt.

// server/tasks/nightly.ts
import { withPingArk } from 'pingark/nuxt'

export default defineTask({
  meta: { name: 'nightly', description: 'Nightly job' },
  run: withPingArk(
    async () => {
      await runNightlyJob()
      return { result: 'ok' }
    },
    { check: 'nightly-job' },
  ),
})
Copy

The same wrapper works on any Nitro event handler, so you can also wrap a server route that a cron service calls.

Capture an exception on failure

When you signal a failure by hand, pass the caught error to fail. Its name, message, and stack trace are attached to the failure, so you can see what went wrong right on the PingArk timeline. (The monitor wrapper does this for you already.)

import { success, fail } from 'pingark'

try {
  await runNightlyJob()
  await success('nightly-job')
} catch (error) {
  await fail('nightly-job', error)
  throw error
}
Copy

Report an exit code

If you already have a process exit status, send it directly. Zero counts as success and any non-zero value counts as a failure, and the raw code is recorded either way.

import { exitCode } from 'pingark'

await exitCode('nightly-job', 137) // 137 is an out-of-memory kill
Copy

Log a progress event

A log event records a note on the timeline without changing the check's state. It never arms, recovers, or alerts. Use it for progress inside a long job, so the timeline tells the whole story.

import { log } from 'pingark'

await log('nightly-job', 'processed 5,000 of 20,000 rows')
Copy

Zero-setup checks

If you would rather not create a check first, PingArk can make one on the very first ping. Add ?create=1 to a ping URL for a slug that does not exist yet and the check is created and armed on the spot.

curl -fsS "https://ping.pingark.com/ping/your-project-ping-key/nightly-job?create=1"
Copy

Auto-provisioned checks get a generic schedule you can refine later in the dashboard. When you want the schedule, timezone, and grace to be right from the start, use the management API instead, which sets them from your real schedule.

The management API client

For setup scripts and internal tooling, createPingArk().api() gives you a small client for the management API. It needs a read-write PINGARK_API_KEY. Unlike the ping signals, the client throws on an error, because a failed setup call is something you want to know about.

import { createPingArk } from 'pingark'

const pingark = createPingArk() // reads PINGARK_API_KEY from the environment

const check = await pingark.api().createCheck({
  name: 'Nightly job',
  slug: 'nightly-job',
  schedule_type: 'simple',
  period: 86400, // expected every 24 hours
  grace: 3600, // allow an hour late before alerting
  timezone: 'UTC',
})

await pingark.api().pause(check.id)
await pingark.api().resume(check.id)

const pings = await pingark.api().pings(check.id)
const flips = await pingark.api().flips(check.id)
const channels = await pingark.api().channels()
Copy

The client covers the whole API: create, read, update, and delete checks, pause and resume them, and read back pings, status changes, and the project's channels. For the full field set and response shapes, see the Management API reference.

How it stays out of your way

Monitoring should never be the reason a job fails. Every ping the SDK sends has a short timeout and swallows its own errors, and it never retries. If PingArk is unreachable, your job runs exactly as it would without the SDK, and the next scheduled run pings again. When the SDK is disabled or the ping key is missing, the signals are a silent no-op, which is what you want on a laptop or in a test run.

One courtesy sits outside that silence: if a ping is skipped because no ping key is configured, the SDK prints a single console notice pointing you at setup. It appears once per process at most, and never when NODE_ENV is production. Setting enabled: false or PINGARK_ENABLED=false asks for silence and gets it, notice included.

The monitor wrapper is careful here too: it reports a thrown error and then re-throws the original, so your own error handling still runs. The one deliberate exception is the api() management client. It is a setup tool, not part of a running job, so it surfaces errors rather than hiding them.

Troubleshooting

  • No pings arriving. Check that PINGARK_PING_KEY is set and that PINGARK_ENABLED is not false. With either missing, the pings are a no-op; outside production a missing key also prints the one-time setup notice to the console.
  • Nothing on a machine you expect to be quiet. Set PINGARK_ENABLED=false on staging and local so only production reports. That is the intended way to mute an environment.
  • A check set to accept only POST. The SDK sends a plain ping as a GET and a ping with a body as a POST. If you configured a check to accept only POST requests, use a signal that carries a body, or relax the filter on the check.
  • The management client throws about a missing key. The api() client needs a read-write PINGARK_API_KEY, which is separate from the ping key.
  • An old CommonJS project. The package ships a CommonJS build too, so const { monitor } = require('pingark') works alongside the ESM import.

Still stuck? The ping API guide explains the signals in full, and the Management API reference covers everything the client can do.

Config reference

Every option can be passed to createPingArk, or set through the matching environment variable. Options always win over the environment.

OptionEnvDefaultWhat it does
enabledPINGARK_ENABLEDtrueMaster switch. Set to false to silence every ping.
baseUrlPINGARK_BASE_URLhttps://ping.pingark.comThe ingestion base URL your pings are sent to.
apiBaseUrlPINGARK_API_BASE_URLhttps://api.pingark.comThe management API base URL, used by the api() client.
pingKeyPINGARK_PING_KEYundefinedThe project ping key your task pings hit.
apiKeyPINGARK_API_KEYundefinedA read-write key, used only by the management API client.
timeoutMsPINGARK_TIMEOUT5000Outbound ping timeout in milliseconds. The env var is read in whole seconds. Short, so a slow network never hangs a job.
userAgentPINGARK_USER_AGENTPingArk-JSThe user agent sent with every ping, so you can spot the SDK in ping history.
defaultCheckPINGARK_DEFAULT_CHECKundefinedAn optional fallback check, so the signal helpers can be called with no argument.