Skip to main content

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.

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

Installation

bash pnpm add @geekflare/api-node @nestjs/config

Set your API key

.env
GEEKFLARE_API_KEY=your_api_key_here
Never commit .env. Add it to .gitignore and use .env.example for documentation.

Module setup

GeekflareService

Create a service that instantiates the client once and exposes typed methods.
src/geekflare/geekflare.service.ts
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

src/geekflare/geekflare.module.ts
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.
src/app.module.ts
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

src/scrape/dto/scrape.dto.ts
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

src/scrape/scrape.controller.ts
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:
curl -X POST http://localhost:3000/scrape \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://toscrape.com/", "renderJS": true }'

DTOs

src/search/dto/search.dto.ts
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

src/search/search.controller.ts
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:
curl -X POST http://localhost:3000/search \
  -H "Content-Type: application/json" \
  -d '{ "query": "best NestJS practices", "limit": 5, "location": "us" }'

Screenshot

DTOs

src/screenshot/dto/screenshot.dto.ts
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

src/screenshot/screenshot.controller.ts
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:
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:
src/app.module.ts
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:
src/main.ts
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:
src/filters/geekflare-exception.filter.ts
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:
app.useGlobalFilters(new GeekflareExceptionFilter());

Common error codes

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

Next steps