npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

geo-ai-nest

v0.1.2

Published

NestJS integration for GEO AI — dynamic module, middleware, controller, guard, interceptor and decorators for llms.txt / AI Search Optimization

Readme

geo-ai-nest

NestJS integration for GEO AI — dynamic module, middleware, controller, guard, interceptor, and decorators for llms.txt / AI Search Optimization (GEO).

Wraps geo-ai-core in NestJS idioms. All GEO logic lives in geo-ai-core; this package only adapts it to NestJS: dynamic modules, dependency injection, middleware, controllers, guards, interceptors, and decorators.

Requirements

  • Node.js >= 20
  • NestJS >= 10 (Express or Fastify adapter)
  • TypeScript >= 5.5

Installation

npm install geo-ai-nest

@nestjs/common and @nestjs/core are peer dependencies — they must already be installed in your project.


Quick Start

forRoot (synchronous)

import { Module } from '@nestjs/common';
import { GeoAIModule } from 'geo-ai-nest';

@Module({
  imports: [
    GeoAIModule.forRoot({
      siteName: 'My Site',
      siteUrl: 'https://example.com',
      provider: {
        docs: [
          { title: 'Getting Started', url: '/docs/start', content: 'Introduction...' },
        ],
      },
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

Once registered, GET /llms.txt, GET /llms-full.txt, and GET /.well-known/llms.txt are served automatically by the middleware, and GET /robots-ai.txt is served by the built-in controller.


forRootAsync (asynchronous / config service)

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { GeoAIModule } from 'geo-ai-nest';

@Module({
  imports: [
    ConfigModule.forRoot(),
    GeoAIModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        siteName: config.get('SITE_NAME'),
        siteUrl: config.get('SITE_URL'),
        provider: { /* ... */ },
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}

forRootAsync also supports useClass and useExisting via the GeoAIOptionsFactory interface.


Configuration

GeoAIOptions extends GeoAIConfig from geo-ai-core with NestJS-specific fields:

| Field | Type | Default | Description | |-------|------|---------|-------------| | siteName | string | required | Site name for llms.txt header | | siteUrl | string | required | Canonical site URL | | provider | ContentProvider \| Record<string, Resource[]> | required | Content source | | siteDescription | string? | — | Optional site description | | crawlers | Record<string, 'allow'\|'disallow'> \| 'all' | — | Per-bot crawler rules | | cache | CacheAdapter \| string | — | Cache adapter or TTL string (e.g. '24h') | | crawlTracking | CrawlTrackingConfig \| true | — | Crawl tracking config | | isGlobal | boolean? | false | Mark module as global | | injectLinkHeader | boolean? | false | Inject Link header on all responses | | cacheMaxAge | number? | 3600 | Cache-Control max-age in seconds | | registerController | boolean? | true | Register GeoAIController (/robots-ai.txt) | | registerMiddleware | boolean? | true | Apply GeoAIMiddleware (llms.txt routes) | | generateTimeout | number? | 30000 | Timeout (ms) for llms.txt generation; returns 500 on expiry |


Components

GeoAIMiddleware

Applied automatically to all routes. Exclusively handles:

  • GET /llms.txtgenerateLlms(false)200 text/plain
  • GET /llms-full.txtgenerateLlms(true)200 text/plain
  • GET /.well-known/llms.txtgenerateLlms(false)200 text/plain

For all other paths it calls next(). When injectLinkHeader: true, it sets the Link header before passing through. AI bot requests trigger fire-and-forget crawl tracking.

Disable with registerMiddleware: false in options.


GeoAIController

Registers GET /robots-ai.txt automatically. Returns text/plain with a Cache-Control: public, max-age={cacheMaxAge} header.

Disable with registerController: false in options.


GeoAIService

Injectable service exposing all geo-ai-core capabilities:

import { Injectable } from '@nestjs/common';
import { GeoAIService } from 'geo-ai-nest';

@Injectable()
export class MyService {
  constructor(private readonly geoAI: GeoAIService) {}

  async getLlms() {
    return this.geoAI.generateLlms();          // standard llms.txt
  }

  async getFullLlms() {
    return this.geoAI.generateLlms(true);      // llms-full.txt
  }

  getRobots() {
    return this.geoAI.generateRobotsTxt();
  }

  getMetaTags() {
    return this.geoAI.generateMetaTags();
  }

  getLinkHeader() {
    return this.geoAI.generateLinkHeader();
  }

  getJsonLd() {
    return this.geoAI.generateJsonLd();
  }

  checkBot(ua: string) {
    return this.geoAI.isAIBot(ua);             // bot name or null
  }

  getEngine() {
    return this.geoAI.getEngine();             // raw GeoAIInstance
  }

  async clearCache() {
    await this.geoAI.invalidateCache();
  }
}

GeoAIGuard

Restricts route access to verified AI bot requests:

import { Controller, Get, UseGuards } from '@nestjs/common';
import { GeoAIGuard } from 'geo-ai-nest';

@Controller('bot-only')
export class BotOnlyController {
  @Get()
  @UseGuards(GeoAIGuard)
  handle() {
    return 'AI bots only';
  }
}

Returns true when the User-Agent matches a known AI bot, false otherwise.


GeoAIInterceptor

Appends the Link header pointing to llms.txt on all responses from the decorated controller or handler:

import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { GeoAIInterceptor } from 'geo-ai-nest';

@Controller()
@UseInterceptors(GeoAIInterceptor)
export class AppController {
  @Get()
  index() {
    return 'Hello';
  }
}

Can also be registered as a global interceptor via app.useGlobalInterceptors(new GeoAIInterceptor(geoAIService)).


Decorators

@IsAIBot()

Parameter decorator that injects the detected bot name (or null) into a handler parameter:

import { Controller, Get } from '@nestjs/common';
import { IsAIBot } from 'geo-ai-nest';

@Controller()
export class AppController {
  @Get()
  index(@IsAIBot() bot: string | null) {
    if (bot) {
      return `Hello, ${bot}`;
    }
    return 'Hello, human';
  }
}

@GeoAIMeta()

Class/method decorator that attaches GEO metadata to a controller or handler via Reflect.metadata:

import { Controller, Get } from '@nestjs/common';
import { GeoAIMeta } from 'geo-ai-nest';

@Controller()
@GeoAIMeta({ section: 'docs', priority: 'high' })
export class DocsController {
  @Get('article')
  @GeoAIMeta({ type: 'article' })
  getArticle() { /* ... */ }
}

Retrieve metadata with Reflector using the GEO_AI_META_KEY constant ('geo-ai-meta').


Re-exported from geo-ai-core

geo-ai-nest re-exports all public symbols from geo-ai-core so you only need one import:

import {
  // Classes
  createGeoAI,
  BotRulesEngine,
  LlmsGenerator,
  SeoGenerator,
  CrawlTracker,
  MemoryCacheAdapter,
  FileCacheAdapter,
  MemoryCrawlStore,
  CryptoService,
  AI_BOTS,
  parseDuration,
  AiGenerator,           // from geo-ai-core/ai

  // Types
  GeoAIInstance,
  GeoAIConfig,
  Resource,
  ResourceSection,
  ContentProvider,
  CacheAdapter,
  CrawlStore,
  // ... and more
} from 'geo-ai-nest';

Fastify Adapter

The middleware and guard include Express/Fastify fallbacks for request/response access. Fastify support is experimental — E2E tests cover Express only. Verify Fastify compatibility manually with @nestjs/platform-fastify.


License

MIT — see LICENSE.