Skip to content

Repository files navigation

@minimalstuff/adonis-waiting

AdonisJS v7 package that intercepts incoming requests while external services are not yet ready, redirects to a waiting route, and lets you through once they are.

The package handles the detection logic. Rendering the waiting page is entirely up to you — Edge, Inertia, or anything else.

How it works

  1. On boot, the provider starts a background interval that runs all registered checkers
  2. The middleware intercepts every request — if not ready, redirects to the configured waiting route (or returns 503 for non-HTML requests)
  3. Your controller receives ready and redirect — render however you want, implement polling or SSE as needed

Installation

node ace configure @minimalstuff/adonis-waiting

This will:

  • Publish config/waiting.ts
  • Generate app/waiting/example_checker.ts
  • Generate app/waiting/waiting_controller.ts (stub to fill in)
  • Register the provider in adonisrc.ts
  • Print instructions for middleware and route wiring

Manual setup

Middleware

Add to the router middleware stack in start/kernel.ts:

router.use([
	() => import('@adonisjs/core/bodyparser_middleware'),
	() => import('@minimalstuff/adonis-waiting/middleware'),
]);

Route

Register one route pointing to your controller in start/routes.ts:

router.get('/waiting', [WaitingController, 'render']);

Writing the waiting controller

The generated stub at app/waiting/waiting_controller.ts gives you ready and redirect. Implement rendering as you see fit:

// Inertia — pairs with usePolling(2000) on the client
async render({ request, inertia }: HttpContext) {
  return inertia.render('waiting/index', {
    ready: this.waitingService.isReady(),
    redirect: request.input('redirect', '/'),
  })
}

// Edge
async render({ request, view }: HttpContext) {
  return view.render('waiting/index', {
    ready: this.waitingService.isReady(),
    redirect: request.input('redirect', '/'),
  })
}

Inertia polling example

// resources/pages/waiting/index.tsx
import { router, usePage } from '@inertiajs/react';
import { useEffect } from 'react';

export default function WaitingPage() {
	const { ready, redirect } = usePage<{ ready: boolean; redirect: string }>()
		.props;

	useEffect(() => {
		if (ready) router.visit(redirect);
	}, [ready, redirect]);

	usePolling(2000);

	return <p>Starting up…</p>;
}

Writing a checker

A checker is any class that implements ServiceChecker:

import type { ServiceChecker } from '@minimalstuff/adonis-waiting';

export class RedisChecker implements ServiceChecker {
	readonly name = 'redis';
	readonly description = 'Redis connection'; // optional, surfaced by getCheckerStatuses()

	async check(): Promise<boolean> {
		try {
			await redis.ping();
			return true;
		} catch {
			return false;
		}
	}
}

The check() method is called every checkInterval ms. Return true when ready, false otherwise. Thrown exceptions are treated as false.

A checker can wrap anything: a database connection, an HTTP health endpoint, a file on disk, a CLI command — anything expressible as an async boolean.

Configuration

// config/waiting.ts
import { defineConfig } from '@minimalstuff/adonis-waiting';
import { RedisChecker } from '#waiting/redis_checker';
import { DatabaseChecker } from '#waiting/database_checker';

export default defineConfig({
	checkers: [new RedisChecker(), new DatabaseChecker()],
	checkInterval: 2_000, // ms between each check cycle, default: 2000
	waitingRoute: '/waiting', // route the middleware redirects to, default: '/waiting'
});

All checkers must return true in the same cycle for the app to be considered ready.

API reference

ServiceChecker

interface ServiceChecker {
	readonly name: string;
	readonly description?: string; // shown by getCheckerStatuses(), purely informational
	readonly enabled?: boolean;
	check(): Promise<boolean>;
}

defineConfig(config: WaitingConfig): WaitingConfig

Type-safe config helper.

WaitingService

Singleton registered in the container. Inject it wherever you need to check readiness state.

import { inject } from '@adonisjs/core';
import { WaitingService } from '@minimalstuff/adonis-waiting';

@inject()
export class WaitingController {
	constructor(private readonly waitingService: WaitingService) {}
}
Method Returns Description
isReady() boolean Whether all checkers passed on the last cycle
getWaitingRoute() string Configured waiting route
getCheckInterval() number Configured check interval in ms
getCheckerStatuses() CheckerStatus[] Per-checker status, in config order

CheckerStatus

Returned by getCheckerStatuses() — one entry per configured checker, useful for rendering a status list on the waiting page.

type CheckerStatus =
	| { name: string; description?: string; state: 'disabled' }
	| { name: string; description?: string; state: 'pending' }
	| {
			name: string;
			description?: string;
			state: 'healthy';
			lastCheckedAt: Date;
	  }
	| {
			name: string;
			description?: string;
			state: 'unhealthy';
			lastCheckedAt: Date;
	  }
	| {
			name: string;
			description?: string;
			state: 'error';
			lastCheckedAt: Date;
			error: string;
	  };
  • disabled — checker has enabled: false, never run
  • pending — checker hasn't completed a first check cycle yet
  • healthy / unhealthy — last check() resolved true / false
  • error — last check() threw or rejected; error holds the message
const checkers = waitingService.getCheckerStatuses();
// [{ name: 'redis', description: 'Redis connection', state: 'healthy', lastCheckedAt: ... }, ...]

WaitingMiddleware

Router middleware. Intercepts requests when not ready:

  • HTML requests → redirect to {waitingRoute}?redirect={originalUrl}
  • Non-HTML requests503 { error: 'E_SERVICE_UNAVAILABLE', retryAfter: 2 }

Behaviour notes

  • If no checkers are configured, the service is considered ready immediately
  • The first check runs at boot — there is a brief window before the first cycle completes where isReady() returns false
  • The middleware does not redirect requests whose URL starts with the configured waitingRoute (avoids redirect loops)
  • The redirect query parameter is your responsibility to sanitise in the controller before passing to the renderer

About

Wait until an external service is available

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages