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

# NestJS

> Integrate Geekflare API into a NestJS application using injectable services, ConfigModule, and typed DTOs.

This guide follows NestJS conventions: a dedicated `GeekflareModule` with an injectable `GeekflareService`, typed request DTOs, and `.env` config via `ConfigModule`. The examples use the official `@geekflare/api-node` SDK.

## Prerequisites

* NestJS **10+**
* Node.js **18+**
* A Geekflare API key — [get one free](https://dash.geekflare.com)

## Installation

<Tabs>
  <Tab title="pnpm">
    `bash pnpm add @geekflare/api-node @nestjs/config `
  </Tab>

  <Tab title="npm">
    `bash npm install @geekflare/api-node @nestjs/config `
  </Tab>
</Tabs>

## Set your API key

```bash .env theme={null}
GEEKFLARE_API_KEY=your_api_key_here
```

<Warning>
  Never commit `.env`. Add it to `.gitignore` and use `.env.example` for
  documentation.
</Warning>

***

## Module setup

### `GeekflareService`

Create a service that instantiates the client once and exposes typed methods.

```ts src/geekflare/geekflare.service.ts theme={null}
import { Injectable, OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { GeekflareClient } from "@geekflare/api-node";

@Injectable()
export class GeekflareService implements OnModuleInit {
  private client: GeekflareClient;

  constructor(private readonly config: ConfigService) {}

  onModuleInit() {
    this.client = new GeekflareClient({
      apiKey: this.config.getOrThrow<string>("GEEKFLARE_API_KEY"),
    });
  }

  async webScrape(params: {
    url: string;
    renderJS?: boolean;
    blockAds?: boolean;
    format?: string;
  }) {
    return this.client.webScrape(params);
  }

  async search(params: {
    query: string;
    limit?: number;
    location?: string;
    source?: string;
    time?: string;
    includeDomains?: string[];
    excludeDomains?: string[];
  }) {
    return this.client.search(params);
  }

  async screenshot(params: {
    url: string;
    type?: string;
    fullPage?: boolean;
    device?: string;
    viewportWidth?: number;
    viewportHeight?: number;
    blockAds?: boolean;
    hideCookie?: boolean;
    quality?: number;
  }) {
    return this.client.screenshot(params);
  }
}
```

### `GeekflareModule`

```ts src/geekflare/geekflare.module.ts theme={null}
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GeekflareService } from "./geekflare.service";

@Module({
  imports: [ConfigModule],
  providers: [GeekflareService],
  exports: [GeekflareService],
})
export class GeekflareModule {}
```

### `AppModule`

Register `ConfigModule` globally so `ConfigService` is available everywhere, then import `GeekflareModule`.

```ts src/app.module.ts theme={null}
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GeekflareModule } from "./geekflare/geekflare.module";

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true }), GeekflareModule],
})
export class AppModule {}
```

***

## Web Scraping

### DTOs

```ts src/scrape/dto/scrape.dto.ts theme={null}
import { IsString, IsUrl, IsOptional, IsBoolean } from "class-validator";

export class ScrapeDto {
  @IsUrl()
  url: string;

  @IsOptional()
  @IsBoolean()
  renderJS?: boolean;

  @IsOptional()
  @IsBoolean()
  blockAds?: boolean;

  @IsOptional()
  @IsString()
  format?: string;
}
```

### Controller

```ts src/scrape/scrape.controller.ts theme={null}
import {
  Body,
  Controller,
  Post,
  HttpException,
  HttpStatus,
} from "@nestjs/common";
import { GeekflareService } from "../geekflare/geekflare.service";
import { ScrapeDto } from "./dto/scrape.dto";

@Controller("scrape")
export class ScrapeController {
  constructor(private readonly geekflare: GeekflareService) {}

  @Post()
  async scrape(@Body() dto: ScrapeDto) {
    try {
      return await this.geekflare.webScrape({
        url: dto.url,
        renderJS: dto.renderJS ?? true,
        blockAds: dto.blockAds ?? true,
        format: dto.format ?? "html,markdown",
      });
    } catch (error: any) {
      throw new HttpException(
        error.message ?? "Scrape failed",
        error?.status ?? HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}
```

**Example request:**

```bash theme={null}
curl -X POST http://localhost:3000/scrape \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://toscrape.com/", "renderJS": true }'
```

***

## Search

### DTOs

```ts src/search/dto/search.dto.ts theme={null}
import {
  IsString,
  IsOptional,
  IsInt,
  IsArray,
  Min,
  Max,
} from "class-validator";

export class SearchDto {
  @IsString()
  query: string;

  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(100)
  limit?: number;

  @IsOptional()
  @IsString()
  location?: string;

  @IsOptional()
  @IsString()
  source?: string;

  @IsOptional()
  @IsString()
  time?: string; // "d" | "w" | "m"

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  includeDomains?: string[];

  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  excludeDomains?: string[];
}
```

### Controller

```ts src/search/search.controller.ts theme={null}
import {
  Body,
  Controller,
  Post,
  HttpException,
  HttpStatus,
} from "@nestjs/common";
import { GeekflareService } from "../geekflare/geekflare.service";
import { SearchDto } from "./dto/search.dto";

@Controller("search")
export class SearchController {
  constructor(private readonly geekflare: GeekflareService) {}

  @Post()
  async search(@Body() dto: SearchDto) {
    try {
      return await this.geekflare.search({
        query: dto.query,
        limit: dto.limit ?? 10,
        location: dto.location ?? "us",
        source: dto.source ?? "web",
        time: dto.time,
        includeDomains: dto.includeDomains,
        excludeDomains: dto.excludeDomains,
      });
    } catch (error: any) {
      throw new HttpException(
        error.message ?? "Search failed",
        error?.status ?? HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}
```

**Example request:**

```bash theme={null}
curl -X POST http://localhost:3000/search \
  -H "Content-Type: application/json" \
  -d '{ "query": "best NestJS practices", "limit": 5, "location": "us" }'
```

***

## Screenshot

### DTOs

```ts src/screenshot/dto/screenshot.dto.ts theme={null}
import {
  IsString,
  IsUrl,
  IsOptional,
  IsBoolean,
  IsInt,
  IsIn,
  Min,
  Max,
} from "class-validator";

export class ScreenshotDto {
  @IsUrl()
  url: string;

  @IsOptional()
  @IsIn(["png", "jpg", "webp"])
  type?: string;

  @IsOptional()
  @IsBoolean()
  fullPage?: boolean;

  @IsOptional()
  @IsIn(["desktop", "mobile"])
  device?: string;

  @IsOptional()
  @IsInt()
  viewportWidth?: number;

  @IsOptional()
  @IsInt()
  viewportHeight?: number;

  @IsOptional()
  @IsBoolean()
  blockAds?: boolean;

  @IsOptional()
  @IsBoolean()
  hideCookie?: boolean;

  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(100)
  quality?: number;
}
```

### Controller

```ts src/screenshot/screenshot.controller.ts theme={null}
import {
  Body,
  Controller,
  Post,
  HttpException,
  HttpStatus,
} from "@nestjs/common";
import { GeekflareService } from "../geekflare/geekflare.service";
import { ScreenshotDto } from "./dto/screenshot.dto";

@Controller("screenshot")
export class ScreenshotController {
  constructor(private readonly geekflare: GeekflareService) {}

  @Post()
  async screenshot(@Body() dto: ScreenshotDto) {
    try {
      return await this.geekflare.screenshot({
        url: dto.url,
        type: dto.type ?? "png",
        fullPage: dto.fullPage ?? true,
        device: dto.device ?? "desktop",
        viewportWidth: dto.viewportWidth ?? 1280,
        viewportHeight: dto.viewportHeight ?? 800,
        blockAds: dto.blockAds ?? true,
        hideCookie: dto.hideCookie ?? true,
        quality: dto.quality ?? 90,
      });
    } catch (error: any) {
      throw new HttpException(
        error.message ?? "Screenshot failed",
        error?.status ?? HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
  }
}
```

**Example request:**

```bash theme={null}
curl -X POST http://localhost:3000/screenshot \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://example.com", "fullPage": true, "type": "png" }'
```

***

## Wiring it all together

Register your feature modules in `AppModule`:

```ts src/app.module.ts theme={null}
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GeekflareModule } from "./geekflare/geekflare.module";
import { ScrapeController } from "./scrape/scrape.controller";
import { SearchController } from "./search/search.controller";
import { ScreenshotController } from "./screenshot/screenshot.controller";

@Module({
  imports: [ConfigModule.forRoot({ isGlobal: true }), GeekflareModule],
  controllers: [ScrapeController, SearchController, ScreenshotController],
})
export class AppModule {}
```

Enable `ValidationPipe` globally in `main.ts` to activate the DTO decorators:

```ts src/main.ts theme={null}
import { NestFactory } from "@nestjs/core";
import { ValidationPipe } from "@nestjs/common";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true, // strip unknown properties
      forbidNonWhitelisted: true,
      transform: true, // auto-transform to DTO types
    }),
  );

  await app.listen(3000);
}
bootstrap();
```

***

## Error handling

NestJS maps `HttpException` to the correct HTTP status automatically. For a global catch-all, add an exception filter:

```ts src/filters/geekflare-exception.filter.ts theme={null}
import {
  ExceptionFilter,
  Catch,
  ArgumentsHost,
  HttpException,
  HttpStatus,
  Logger,
} from "@nestjs/common";

@Catch()
export class GeekflareExceptionFilter implements ExceptionFilter {
  private readonly logger = new Logger(GeekflareExceptionFilter.name);

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const error = exception as any;

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : (error?.apiCode ?? HttpStatus.INTERNAL_SERVER_ERROR);

    const message =
      exception instanceof HttpException
        ? exception.message
        : (error?.message ?? "Internal server error");

    this.logger.error(`Geekflare API error [${status}]: ${message}`);

    response.status(status).json({
      statusCode: status,
      error: message,
      timestamp: new Date().toISOString(),
    });
  }
}
```

Register it in `main.ts`:

```ts theme={null}
app.useGlobalFilters(new GeekflareExceptionFilter());
```

### Common error codes

| Code  | Meaning                             |
| ----- | ----------------------------------- |
| `401` | Missing or invalid `x-api-key`      |
| `402` | API credits exhausted               |
| `403` | Endpoint not available on your plan |
| `404` | Wrong endpoint URL or HTTP method   |
| `429` | Rate limit exceeded                 |

***

## Next steps

* [Cloudflare Workers quickstart](/api/quickstart/cloudflare-workers) — Edge-native `fetch` requests
* [Python quickstart](/api/quickstart/python) — Official Python SDK
* [API Reference](/api/endpoint/reference) — Full endpoint documentation
* [SDK on npm](https://www.npmjs.com/package/@geekflare/api-node)
