> ## Documentation Index
> Fetch the complete documentation index at: https://docs.spidra.io/llms.txt
> Use this file to discover all available pages before exploring further.

# NestJS

> Use Spidra's web scraping, site crawling, SERP search, and document parsing in NestJS with dynamic modules, injectable services, and webhook guards.

The Spidra NestJS integration (`spidra-nestjs`) provides a native NestJS module and injectable service for the Spidra platform:

* **`SpidraModule.forRoot()` & `forRootAsync()`**: Dynamic module registration with sync or async configuration (e.g. via `ConfigService`).
* **`SpidraService`**: Injectable service with typed accessors and dynamic proxy delegation for all 70+ Spidra client methods.
* **`@InjectSpidra()`**: Parameter decorator to inject the underlying `SpidraClient`.
* **`SpidraWebhookGuard`**: Controller guard that verifies HMAC-SHA256 signatures on incoming asynchronous webhooks.

## Installation

```bash theme={null}
npm install spidra-nestjs spidra
```

Requires Node.js 18+ and NestJS 9+.

> [!NOTE]
>
> Get your API key from [app.spidra.io](https://app.spidra.io) under **API Keys**.
> Store it in your environment or NestJS `ConfigService`. Never hardcode keys.

## Configuration

Import `SpidraModule` into your root `AppModule`:

### Synchronous configuration

```ts theme={null}
import { Module } from "@nestjs/common";
import { SpidraModule } from "spidra-nestjs";

@Module({
  imports: [
    SpidraModule.forRoot({
      apiKey: process.env.SPIDRA_API_KEY,
      baseUrl: process.env.SPIDRA_BASE_URL,
    }),
  ],
})
export class AppModule {}
```

### Asynchronous configuration (with `ConfigService`)

```ts theme={null}
import { Module } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { SpidraModule } from "spidra-nestjs";

@Module({
  imports: [
    ConfigModule.forRoot(),
    SpidraModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        apiKey: config.get<string>("SPIDRA_API_KEY"),
        baseUrl: config.get<string>("SPIDRA_BASE_URL"),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class AppModule {}
```

## Quickstart

### 1. In a service

Inject `SpidraService` into any provider or controller:

```ts theme={null}
import { Injectable } from "@nestjs/common";
import { SpidraService } from "spidra-nestjs";

@Injectable()
export class ScraperService {
  constructor(private readonly spidra: SpidraService) {}

  async scrapeProduct(url: string) {
    return await this.spidra.scrape({
      urls: [{ url }],
      prompt: "Extract product title, price, and availability",
      output: "json",
    });
  }
}
```

### 2. In a controller with Webhook Guard

Protect webhook receiver endpoints with `SpidraWebhookGuard`:

```ts theme={null}
import { Controller, Post, Body, UseGuards } from "@nestjs/common";
import { SpidraWebhookGuard } from "spidra-nestjs";

@Controller("webhooks")
export class WebhookController {
  @Post("spidra")
  @UseGuards(SpidraWebhookGuard)
  handleWebhook(@Body() event: any) {
    console.log("Verified Spidra webhook received:", event.event);

    if (event.event === "crawl.completed") {
      console.log(`Crawl job ${event.jobId} finished!`);
    }

    return { received: true };
  }
}
```

## Scraping & browser actions

```ts theme={null}
@Injectable()
export class AuthService {
  constructor(private readonly spidra: SpidraService) {}

  async scrapeAuthenticatedPage(url: string) {
    return await this.spidra.scrape({
      urls: [
        {
          url,
          actions: [
            { type: "type", selector: "#user", value: "admin" },
            { type: "type", selector: "#pass", value: "secret" },
            { type: "click", selector: "button[type='submit']" },
            { type: "waitFor", selector: ".dashboard" },
          ],
        },
      ],
      prompt: "Extract dashboard metrics",
      output: "json",
      useProxy: true,
      proxyCountry: "us",
    });
  }
}
```

## Deep site crawling

```ts theme={null}
@Injectable()
export class CrawlerService {
  constructor(private readonly spidra: SpidraService) {}

  async crawlDocumentation(domainUrl: string) {
    return await this.spidra.crawl({
      baseUrl: domainUrl,
      crawlInstruction: "Follow guides and documentation pages only",
      transformInstruction: "Extract page title and summary",
      maxPages: 30,
      includePaths: ["/docs/*"],
    });
  }
}
```

## Live web & SERP search

```ts theme={null}
@Injectable()
export class SearchService {
  constructor(private readonly spidra: SpidraService) {}

  async searchLiveWeb(query: string) {
    return await this.spidra.search({
      query,
      sources: ["web", "news"],
      limit: 10,
    });
  }
}
```

## Parallel batch scraping & streaming watchers

```ts theme={null}
@Injectable()
export class BatchService {
  constructor(private readonly spidra: SpidraService) {}

  async scrapeMultiple(urls: string[]) {
    const batch = await this.spidra.startBatchScrape({
      urls,
      prompt: "Extract title and meta description",
    });

    const watcher = this.spidra.client.watchBatch(batch.batchId);
    watcher.on("item", (item) => {
      console.log(`Item completed: ${item.url}`);
    });

    return batch;
  }
}
```

## Domain mapping

```ts theme={null}
@Injectable()
export class MappingService {
  constructor(private readonly spidra: SpidraService) {}

  async mapDomain(url: string) {
    return await this.spidra.map({
      url,
      limit: 100,
    });
  }
}
```

## Document parsing (PDF, Word, Excel)

```ts theme={null}
import { readFile } from "fs/promises";

@Injectable()
export class DocumentService {
  constructor(private readonly spidra: SpidraService) {}

  async parsePdf(filePath: string) {
    const bytes = await readFile(filePath);
    return await this.spidra.parse({
      file: bytes,
      fileName: "report.pdf",
    }, {
      prompt: "Extract invoice total, date, and vendor",
    });
  }
}
```

## Turnkey templates & monitors

```ts theme={null}
@Injectable()
export class LeadService {
  constructor(private readonly spidra: SpidraService) {}

  async getLeads(query: string, area: string) {
    return await this.spidra.runTemplate("google-maps-leads", {
      query,
      areas: [area],
    });
  }

  async setupMonitor(targetUrl: string, webhookUrl: string) {
    return await this.spidra.createMonitor({
      name: "Price Watch",
      sources: [{ kind: "urls", urls: [targetUrl] }],
      schedule: { every: "1d", at: "08:00" },
      alertOn: "page",
      goal: "Alert on any price change",
      notify: { webhook: { url: webhookUrl } },
    });
  }
}
```

## Error handling

```ts theme={null}
import {
  SpidraAuthenticationError,
  SpidraRateLimitError,
  SpidraPaymentRequiredError,
  SpidraError,
} from "spidra-nestjs";

try {
  await this.spidra.scrape({ urls: [{ url: "https://example.com" }] });
} catch (err) {
  if (err instanceof SpidraAuthenticationError) {
    // 401 Unauthorized
  } else if (err instanceof SpidraRateLimitError) {
    // 429 Rate Limited
  } else if (err instanceof SpidraPaymentRequiredError) {
    // 402 Credits Exhausted
  } else if (err instanceof SpidraError) {
    // 5xx / API error
  }
}
```

> [card] [Express](https://docs.spidra.io/sdks/express.md)
>
> Express.js middleware, turnkey router, and error handling.

> [card] [Fastify](https://docs.spidra.io/sdks/fastify.md)
>
> Fastify high-performance plugin and route decoration.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.