DEV Community

Cover image for NestJS Module for AI Search Optimization
Made Büro
Made Büro

Posted on

NestJS Module for AI Search Optimization

Over the past few weeks I’ve been building GEO AI an Open Source ecosystem around AI Search Optimization.

The core idea is simple: if AI systems are going to read and reason about websites, then websites need a clearer way to expose structured content, crawler rules, and machine-readable signals.

Why a separate package for NestJS?

NestJS developers expect framework-native primitives: modules, DI, middleware, guards, interceptors, decorators. Asking them to manually call createGeoAI() and wire everything by hand would feel wrong.

So instead of a generic adapter, I built geo-ai-nest as a thin NestJS layer on top of geo-ai-core. All the GEO logic stays in the core package. The Nest package only adapts it to how Nest apps are structured.

Quick start
npm install geo-ai-nest geo-ai-core

Register the module:

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

@Module({
  imports: [
    GeoAIModule.forRoot({
      siteName: 'My App',
      siteUrl: 'https://myapp.com',
      provider: {
        pages: [
          { title: 'About', url: '/about', description: 'About us' },
          { title: 'Pricing', url: '/pricing', description: 'Our plans' },
        ],
      },
      crawlers: 'all',
    }),
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

That's it. Your app now serves:

  • GET /llms.txt — compact content index
  • GET /llms-full.txt — full content with descriptions
  • GET /.well-known/llms.txt — standard discovery path
  • GET /robots-ai.txt — per-bot crawler rules
  • No extra controllers needed.

Async configuration

Real apps usually load config from a database or config service. forRootAsync handles that:

GeoAIModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    siteName: config.get('SITE_NAME'),
    siteUrl: config.get('SITE_URL'),
    provider: { pages: [] }, // your ContentProvider here
    crawlers: 'all',
    injectLinkHeader: true,
    cacheMaxAge: 7200,
  }),
  inject: [ConfigService],
}),
Enter fullscreen mode Exit fullscreen mode

Detecting AI bots in your routes

The @IsAIBot() parameter decorator tells you which AI crawler is hitting your endpoint — or null if it's a regular user:

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

@Controller('api')
export class CatalogController {
  @Get('products')
  getProducts(@IsAIBot() bot: string | null) {
    if (bot) {
      console.log(`AI crawler detected: ${bot}`);
      // return enriched response for AI consumption
    }
    return this.productService.findAll();
  }
}
Enter fullscreen mode Exit fullscreen mode

Protecting routes from non-AI traffic

GeoAIGuard restricts a route to verified AI bot requests only:

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

@UseGuards(GeoAIGuard)
@Get('ai-only')
getAiContent() {
  return { data: 'Only AI crawlers can see this' };
}
Enter fullscreen mode Exit fullscreen mode

Why middleware for llms.txt instead of a controller?
This was a deliberate choice. Middleware runs before the routing layer, which means:

  • It intercepts requests before guards, interceptors, or pipes run
  • It works regardless of how the app's routing is structured
  • It avoids conflicts with catch-all routes or global prefixes

The robots-ai.txt endpoint uses a controller because it's a standard REST resource that benefits from Nest's decorator pipeline.

Where it fits in GEO AI

At this point, the GEO AI ecosystem looks something like this:

  • geo-ai-core — universal engine
  • geo-ai-next — Next.js integration
  • geo-ai-nest — NestJS integration
  • geo-ai-cli — CLI for any Node.js project

The core package remains the center of gravity, and the framework packages are really adapters around it. The main GEO AI README now describes geo-ai-nest as the NestJS wrapper alongside the Next.js and CLI packages. 

I like this structure because it keeps the real logic in one place while still making adoption feel native inside each framework.

What I wanted from this release
More than anything, I wanted NestJS developers to be able to say:

“Install one package, register one module, and get the GEO surface area in a way that feels like Nest.”

That includes:

  • dynamic module registration
  • automatic llms routes
  • a built-in robots endpoint
  • DI-friendly service access
  • optional route protection for AI bots
  • optional response header injection
  • decorators that fit how Nest apps are already written

That was the bar.

Links
Website
Docs
GitHub
npm

Top comments (0)