Skip to content

service-katalog

Catalog service for KDK-based applications.

Overview

service-katalog is a Feathers microservice — built on top of the kdk-ekosystem packages (@kalisio/kdk-core-api for the base application and @kalisio/kdk-map-api for the catalog service and geospatial layer model) — that serves a catalog of map layers to KDK-based applications. It centralizes the definition of:

  • Layers — the base maps, overlays and terrain layers that an application can display (base maps, weather, hydrography, administrative boundaries, sensors, …).
  • Categories — how those layers are grouped and presented in the application UI.
  • Sublegends — additional legend entries attached to the catalog.

Instead of each application embedding its own hard-coded list of layers, the layers are described once as data in service-katalog and exposed through a single catalog service. The service is published over @kalisio/feathers-distributed, so any other service or application sharing the same distribution key can discover and query the catalog remotely, without a direct HTTP coupling.

Layer definitions live in MongoDB (and can therefore be created, patched and removed at runtime), while categories and sublegends are injected from the configuration files on startup.

Developing

Source layout

FileResponsibility
src/main.jsEntry point — calls createServer() and exits on failure.
src/server.jscreateServer(config) factory: builds the KDK app, configures distribution, loads the catalog, registers the services and starts listening.
src/layers.jsloadLayers / loadCategories / loadSublegends — glob and evaluate the config/**/*.cjs files.
src/routes.jsPlain HTTP routes — currently the healthcheck endpoint.
src/hooks.jsApplication-level Feathers hooks running for every service (empty for now).
src/channels.jsReal-time event channels.
src/middlewares.jsnotFound and errorHandler Express middlewares — always configured last.

The catalog service itself is not declared locally: it comes from @kalisio/kdk-map-api, along with the Winston logger which is set up by createApplication from the logs configuration key.

createServer(config) accepts an object whose keys override the loaded configuration — the tests use it to pick a free port and to disable distribution (createServer({ port: 0, distribution: false })).

Startup flow

On createServer() the service performs the following steps:

createDefaultCatalogLayers creates each configured layer in MongoDB if it is missing, and replaces it otherwise — so the configuration files are the source of truth on every restart, while anything added through the API in the meantime is preserved. createCatalogFeaturesServices then registers a feature service for every layer declaring a service property.

Configuration files

All catalog content is described as plain CommonJS modules under config/, each exporting a function that returns an array of definitions:

config/
├── default.cjs              # app + distribution configuration
├── layers/                  # 28 files grouped by theme
│   ├── basemap/             # ign, osm, cesium, imagery, k2
│   ├── weather/             # forecast, awc, weatherlink, meteofrance, meteoradar
│   ├── hydrography/         # hubeau, flood, vigicrues
│   ├── administrative/      # osm-boundaries, adminexpress, demography
│   ├── atmospheric/         # icos, openaq
│   ├── fire/                # firms
│   ├── infrastructure/      # centipede, rte
│   ├── lab/                 # lab
│   ├── marine/              # maritime, openseamap
│   ├── radioactivity/       # openradiation, teleray
│   └── shot/                # mapillary, panoramax
├── categories/              # 14 files — how layers are grouped in the UI
└── sublegends/              # 11 files — extra legend entries

The loaders glob every *.cjs file in the corresponding directory and call it with a context object so that endpoints can be templated from the environment:

js
// layers.js — context passed to each layer file
const context = { wmtsUrl, tmsUrl, wmsUrl, wcsUrl, k2Url, s3Url, ...app.get('catalog') }

A layer file therefore looks like:

js
module.exports = function ({ wmtsUrl, tmsUrl, wmsUrl, wcsUrl, k2Url, s3Url }) {
  return [{
    name: 'Layers.WIND_TILED',
    type: 'OverlayLayer',
    tags: ['weather', 'forecast'],
    i18n: { /* fr / en labels */ },
    // …
  }]
}

Categories receive the same context plus domain, and sublegends receive no context.

To add a new layer, drop a *.cjs file (or extend an existing one) under the relevant config/layers/<theme>/ directory — it is picked up automatically on the next start.

Installation

Prerequisites

This package is part of the services-ekosystem pnpm workspace and depends on the kdk-ekosystem packages (@kalisio/kdk-core-api, @kalisio/kdk-map-api) which are referenced as local links.

Install

bash
# from the repository root
pnpm install

Run

bash
# from packages/service-katalog
pnpm dev      # start in watch mode (node --watch src/main.js)
pnpm build    # produce the dist/ bundle with Vite

By default the service listens on port 8187, exposes its API under /api and connects to mongodb://127.0.0.1:27017/katalog.

Configuration

The service is configured through @feathersjs/configuration, i.e. config/default.cjs overridden by environment variables.

Application settings

KeyDefaultDescription
apiPath/apiBase path for services (the catalog is exposed at api/catalog), from API_PREFIX.
hostlocalhostBind host (HOSTNAME env var).
port8187Listening port (PORT env var).
baseUrlhttp://localhost:8187/apiPublic URL of the API (BASE_URL env var).
httpsnullSet it to { key, cert } to serve over HTTPS instead of HTTP.
db.urlmongodb://127.0.0.1:27017/katalogMongoDB connection string.
paginate{ default: 10, max: 50 }Application-wide pagination. The catalog service overrides it with { default: 1000, max: 1000 }, and the layer feature services with { default: 5000, max: 5000 }.
origins['http://localhost:3030']CORS origins.
logsConsole + daily rotating fileWinston transports, the files being written to logs/ for 30 days.

The service does not configure authentication: reached directly on its own port, the API answers without a token. Access control is expected to be enforced by the API gateway in front of it.

Feathers Distributed

service-katalog publishes its services on the distribution bus so remote consumers can use them without a direct HTTP call. The relevant block of config/default.cjs:

js
distribution: {
  key: 'katalog',                 // this service's own distribution identity
  authentication: false,
  publicationDelay: 5000,
  heartbeatInterval: 10000,
  timeout: 30000,
  services: (service) => true,    // publish every local service on the bus
  distributedMethods: ['find', 'get', 'create', 'update', 'patch', 'remove'],
  distributedEvents: ['created', 'updated', 'patched', 'removed'],
  middlewares: { after: express.errorHandler() }
}

Three options control distribution:

  • key — this application's own identity. Every service service-katalog publishes is tagged with this key ('katalog').
  • services — a predicate selecting which local services to publish. service-katalog publishes all of them (() => true).
  • remoteServices — a predicate (used by consumers) selecting which remote services to consume.

A consumer discovers service-katalog by matching the producer's key in its remoteServices predicate — the consumer's own key is just its own identity and does not need to equal 'katalog':

js
import distribution from '@kalisio/feathers-distributed'

consumer.configure(distribution({
  key: 'my-app',                                      // the consumer's own identity (arbitrary)
  services: () => false,                              // this consumer publishes nothing
  remoteServices: (service) => service.key === 'katalog'  // consume service-katalog's services
}))

// once discovered, the catalog is available as a normal Feathers service
const layers = await consumer.service('api/catalog').find({
  query: { type: 'OverlayLayer', tags: { $in: ['administrative'] } }
})

A consumer whose remoteServices predicate does not match service.key === 'katalog' will not discover the catalog services.

Environment variables

Here are the environment variables you can use to customize the service:

VariableDescriptionDefaults
HOSTNAMEThe hostname to be used when exposing the servicelocalhost
PORTThe port to be used when exposing the service8187
API_PREFIXThe path prefix under which the services are exposed/api
BASE_URLThe URL used when exposing the servicebuilt from HOSTNAME, PORT and API_PREFIX
NODE_ENVSet it to development to enable verbose logging
VERSIONOverrides the version reported by healthcheckpackage version
BUILD_NUMBERWhen set, added as buildNumber to the healthcheck response
SUBDOMAINThe base domain used to build the default map URLs (e.g. wmtsUrl = https://mapcache.<SUBDOMAIN>/mapcache/wmts/1.0.0)test.kalisio.xyz
API_GATEWAY_URLWhen set, all map URLs are derived from this single gateway instead of the per-service hosts (e.g. wmtsUrl = <API_GATEWAY_URL>/wmts/1.0.0)
K2_URLExplicit override for the K2 endpointderived from SUBDOMAIN / API_GATEWAY_URL
S3_URLExplicit override for the S3 endpoint
LAYERS_FILTERSelects which layers are loaded into the catalog, by layer name (see below)*

LAYERS_FILTER is a space- or comma-separated list of minimatch patterns matched against each layer's name (the Layers. prefix is stripped). A pattern prefixed with - excludes matching layers:

bash
LAYERS_FILTER='*'                 # all layers (default)
LAYERS_FILTER='WIND*'             # only layers whose name starts with WIND
LAYERS_FILTER='* -WIND_TILED'     # every layer except WIND_TILED

API

Beside the distribution bus, service-katalog exposes its content over HTTP under the API prefix (/api by default):

  • a healthcheck endpoint returning the service name and its version;
  • the catalog service, providing full CRUD over the layers, categories and sublegends (GET, POST, PUT, PATCH, DELETE on api/catalog);
  • one feature service per layer declaring a service property, exposing the layer's GeoJSON features under api/<service> — e.g. api/hubeau-hydro-observations.

The full request/response schema — the catalog object structure, the query parameters accepted by find and the error codes — is documented on the API reference page. The spec deliberately declares no server, so pick your own instance URL in the server selector before sending a request from that page.

Deploying

This service is designed to be deployed using the Kargo project.

Testing

Tests are written with Vitest and require a reachable MongoDB instance (see config/test.json).

bash
# from packages/service-katalog
pnpm test     # vitest run --coverage

Three suites are provided:

  • test/app.test.js — boots the server and checks that unknown routes return a 404 JSON error and that the catalog service is populated with layers on startup.
  • test/healthcheck.test.js — checks the healthcheck endpoint, including the buildNumber added when BUILD_NUMBER is set.
  • test/distribution.test.js — spins up a remote consumer and verifies service discovery over feathers-distributed, layer/category/sublegend queries, full CRUD via distribution, and that a consumer with the wrong key cannot discover the services.

The first two boot the server with createServer({ port: 0, distribution: false }), so they need MongoDB but no free fixed port and no distribution bus.

License

Licensed under the MIT license.

Copyright (c) 2026-present Kalisio

Authors

This project is sponsored by

Kalisio