Skip to content

Queries

Queries represent read operations - requests for information without changing application state.

Queries also fit perfectly into the CQRS pattern (Command Query Responsibility Segregation), where reads and writes are separated for better scalability and maintainability. But keep it simple for your use case and needs. CQRS in an option, but not required.

Example Application

The examples on this page reference the hono-demo application.

You can find the full example on GitHub: hono-demo

Key Characteristics

  • Read Operations: Queries fetch data without modifying state
  • Idempotent: Multiple executions return the same result (if data hasn't changed)
  • Type-Safe: Queries are fully typed and validated using Zod
  • Optimized for Reading: Can use specialized read models or databases

Query Structure

A query in Nimbus follows the CloudEvents specification and consists of:

typescript
type Query<TData = unknown> = {
    specversion: "1.0";
    id: string;
    correlationid: string;
    time: string;
    source: string;
    type: string;
    data: TData;
    datacontenttype?: string;
    dataschema?: string;
};
PropertyDescription
specversionThe CloudEvents specification version (always '1.0')
idA globally unique identifier for the query
correlationidA unique identifier to correlate this query with related messages
timeISO 8601 timestamp when the query was created
sourceA URI reference identifying the system creating the query
typeThe query type following CloudEvents naming (e.g., at.overlap.nimbus.get-user)
dataThe query parameters (e.g., filters, pagination)
datacontenttypeOptional MIME type of the data (defaults to application/json)
dataschemaOptional URL to the schema the data adheres to

Query Schema

Nimbus provides a base Zod schema for validating queries:

typescript
import { querySchema } from "@nimbus/core";
import { z } from "zod";

// Extend the base schema with your specific query type and data
const getUserQuerySchema = querySchema.extend({
    type: z.literal("at.overlap.nimbus.get-user"),
    data: z.object({
        id: z.string().length(24),
    }),
});

type GetUserQuery = z.infer<typeof getUserQuerySchema>;

Create Queries

You can create queries using the createQuery() helper:

typescript
import { createQuery } from "@nimbus/core";
import { GetUserQuery } from "./getUser.query.ts";

const query = createQuery<GetUserQuery>({
    type: "at.overlap.nimbus.get-user",
    source: "nimbus.overlap.at",
    data: {
        id: "123",
    },
});

The createQuery() helper automatically generates default values for:

  • id - A unique ULID
  • correlationid - A unique ULID (if not provided)
  • time - Current ISO timestamp
  • specversion - Always '1.0'
  • datacontenttype - Defaults to 'application/json'

Routing Queries

Queries are routed to handlers using the MessageRouter. See the Router documentation for details on registering handlers and routing messages.