guide 12 min read

SERP API Node.js Implementation: Complete Guide with Express & Next.js

Master SERP API integration in Node.js. Learn Express, Next.js, and TypeScript implementations with async/await, error handling, and production-ready code examples.

SERPpost Team
SERP API Node.js Implementation: Complete Guide with Express & Next.js

SERP API Node.js Implementation: Complete Guide 2025

Node.js is the perfect runtime for building scalable SERP API applications. This guide covers everything from basic integration to production-ready implementations with Express, Next.js, and TypeScript.

Why Node.js for SERP API?

Node.js offers unique advantages for SERP API integration:

  • Non-blocking I/O: Perfect for handling multiple concurrent API requests
  • JavaScript ecosystem: Seamless integration with frontend frameworks
  • NPM packages: Rich library ecosystem for HTTP, data processing, and more
  • Serverless ready: Deploy to AWS Lambda, Vercel, or Netlify
  • TypeScript support: Type-safe API integration

Quick Start: Basic Node.js Integration

Installation

npm install axios dotenv
# or
yarn add axios dotenv

Basic Implementation

// serp-client.js
const axios = require('axios');
require('dotenv').config();

class SERPpostClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseURL = 'https://api.serppost.com/v1';
    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async search(engine, query, options = {}) {
    try {
      const response = await this.client.post('/search', {
        engine,
        q: query,
        ...options
      });
      return response.data;
    } catch (error) {
      console.error('Search error:', error.message);
      throw error;
    }
  }

  async googleSearch(query, options = {}) {
    return this.search('google', query, options);
  }

  async bingSearch(query, options = {}) {
    return this.search('bing', query, options);
  }
}

// Usage
const client = new SERPpostClient(process.env.SERPPOST_API_KEY);

async function main() {
  const results = await client.googleSearch('nodejs tutorial');
  console.log(`Found ${results.organic_results.length} results`);
  
  results.organic_results.forEach((result, i) => {
    console.log(`${i + 1}. ${result.title}`);
    console.log(`   ${result.link}`);
  });
}

main();

TypeScript Implementation

For type-safe development:

// types.ts
export interface SearchOptions {
  location?: string;
  gl?: string;
  hl?: string;
  num?: number;
}

export interface OrganicResult {
  position: number;
  title: string;
  link: string;
  snippet: string;
  displayed_link?: string;
}

export interface SearchResponse {
  search_metadata: {
    id: string;
    status: string;
    created_at: string;
  };
  search_parameters: {
    engine: string;
    q: string;
  };
  organic_results: OrganicResult[];
}

// serp-client.ts
import axios, { AxiosInstance } from 'axios';

export class SERPpostClient {
  private client: AxiosInstance;

  constructor(private apiKey: string) {
    this.client = axios.create({
      baseURL: 'https://api.serppost.com/v1',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      }
    });
  }

  async search(
    engine: 'google' | 'bing',
    query: string,
    options: SearchOptions = {}
  ): Promise<SearchResponse> {
    const response = await this.client.post<SearchResponse>('/search', {
      engine,
      q: query,
      ...options
    });
    return response.data;
  }

  async batchSearch(
    queries: Array<{ engine: 'google' | 'bing'; query: string; options?: SearchOptions }>
  ): Promise<SearchResponse[]> {
    const promises = queries.map(({ engine, query, options }) =>
      this.search(engine, query, options)
    );
    return Promise.all(promises);
  }
}

Express.js Integration

Build a REST API with Express:

// server.js
const express = require('express');
const { SERPpostClient } = require('./serp-client');
require('dotenv').config();

const app = express();
app.use(express.json());

const serpClient = new SERPpostClient(process.env.SERPPOST_API_KEY);

// Search endpoint
app.post('/api/search', async (req, res) => {
  try {
    const { engine = 'google', query, ...options } = req.body;

    if (!query) {
      return res.status(400).json({ error: 'Query is required' });
    }

    const results = await serpClient.search(engine, query, options);
    res.json(results);
  } catch (error) {
    console.error('Search error:', error);
    res.status(500).json({ error: 'Search failed' });
  }
});

// Batch search endpoint
app.post('/api/batch-search', async (req, res) => {
  try {
    const { queries } = req.body;

    if (!Array.isArray(queries) || queries.length === 0) {
      return res.status(400).json({ error: 'Queries array is required' });
    }

    const results = await serpClient.batchSearch(queries);
    res.json(results);
  } catch (error) {
    console.error('Batch search error:', error);
    res.status(500).json({ error: 'Batch search failed' });
  }
});

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok' });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Next.js API Routes

Integrate with Next.js for full-stack applications:

// pages/api/search.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { SERPpostClient } from '@/lib/serp-client';

const client = new SERPpostClient(process.env.SERPPOST_API_KEY!);

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  try {
    const { engine = 'google', query, ...options } = req.body;

    if (!query) {
      return res.status(400).json({ error: 'Query is required' });
    }

    const results = await client.search(engine, query, options);
    res.status(200).json(results);
  } catch (error) {
    console.error('Search error:', error);
    res.status(500).json({ error: 'Search failed' });
  }
}

Next.js Server Component

// app/search/page.tsx
import { SERPpostClient } from '@/lib/serp-client';

async function getSearchResults(query: string) {
  const client = new SERPpostClient(process.env.SERPPOST_API_KEY!);
  return await client.search('google', query);
}

export default async function SearchPage({
  searchParams
}: {
  searchParams: { q: string }
}) {
  const results = await getSearchResults(searchParams.q);

  return (
    <div>
      <h1>Search Results for: {searchParams.q}</h1>
      <ul>
        {results.organic_results.map((result, i) => (
          <li key={i}>
            <h2>{result.title}</h2>
            <a href={result.link}>{result.displayed_link}</a>
            <p>{result.snippet}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}

Advanced Features

Rate Limiting with Redis

const Redis = require('ioredis');
const redis = new Redis();

class RateLimiter {
  constructor(maxRequests, windowMs) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
  }

  async checkLimit(userId) {
    const key = `rate_limit:${userId}`;
    const current = await redis.incr(key);

    if (current === 1) {
      await redis.pexpire(key, this.windowMs);
    }

    return current <= this.maxRequests;
  }
}

// Middleware
const limiter = new RateLimiter(100, 60000); // 100 requests per minute

app.use(async (req, res, next) => {
  const userId = req.headers['x-user-id'] || req.ip;
  const allowed = await limiter.checkLimit(userId);

  if (!allowed) {
    return res.status(429).json({ error: 'Rate limit exceeded' });
  }

  next();
});

Caching with Redis

class CachedSERPClient extends SERPpostClient {
  constructor(apiKey, redis) {
    super(apiKey);
    this.redis = redis;
    this.cacheTTL = 3600; // 1 hour
  }

  async search(engine, query, options = {}) {
    const cacheKey = `serp:${engine}:${query}:${JSON.stringify(options)}`;
    
    // Check cache
    const cached = await this.redis.get(cacheKey);
    if (cached) {
      return JSON.parse(cached);
    }

    // Fetch from API
    const results = await super.search(engine, query, options);

    // Store in cache
    await this.redis.setex(cacheKey, this.cacheTTL, JSON.stringify(results));

    return results;
  }
}

Error Handling & Retry Logic

const axios = require('axios');
const axiosRetry = require('axios-retry');

class RobustSERPClient extends SERPpostClient {
  constructor(apiKey) {
    super(apiKey);
    
    // Configure retry logic
    axiosRetry(this.client, {
      retries: 3,
      retryDelay: axiosRetry.exponentialDelay,
      retryCondition: (error) => {
        return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
               error.response?.status === 429;
      }
    });
  }

  async search(engine, query, options = {}) {
    try {
      return await super.search(engine, query, options);
    } catch (error) {
      if (error.response) {
        // API error
        throw new Error(`API Error: ${error.response.status} - ${error.response.data.message}`);
      } else if (error.request) {
        // Network error
        throw new Error('Network error: No response received');
      } else {
        // Other error
        throw new Error(`Error: ${error.message}`);
      }
    }
  }
}

Performance Optimization

Connection Pooling

const http = require('http');
const https = require('https');

const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 50
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 50
});

const client = axios.create({
  httpAgent,
  httpsAgent,
  timeout: 30000
});

Parallel Requests

async function compareEngines(query) {
  const [googleResults, bingResults] = await Promise.all([
    client.googleSearch(query),
    client.bingSearch(query)
  ]);

  return {
    google: googleResults.organic_results.length,
    bing: bingResults.organic_results.length
  };
}

Real-World Use Cases

SEO Rank Tracker

class RankTracker {
  constructor(serpClient) {
    this.client = serpClient;
  }

  async trackKeyword(keyword, domain, engine = 'google') {
    const results = await this.client.search(engine, keyword, { num: 100 });
    
    const position = results.organic_results.findIndex(result =>
      result.link.includes(domain)
    );

    return {
      keyword,
      domain,
      engine,
      position: position === -1 ? null : position + 1,
      timestamp: new Date()
    };
  }

  async trackMultipleKeywords(keywords, domain) {
    const promises = keywords.map(keyword =>
      this.trackKeyword(keyword, domain)
    );
    return Promise.all(promises);
  }
}

Content Gap Analysis

async function findContentGaps(yourDomain, competitorDomain, keywords) {
  const gaps = [];

  for (const keyword of keywords) {
    const results = await client.googleSearch(keyword, { num: 20 });
    
    const yourRank = results.organic_results.findIndex(r =>
      r.link.includes(yourDomain)
    );
    
    const competitorRank = results.organic_results.findIndex(r =>
      r.link.includes(competitorDomain)
    );

    if (competitorRank !== -1 && (yourRank === -1 || yourRank > competitorRank)) {
      gaps.push({
        keyword,
        yourRank: yourRank === -1 ? null : yourRank + 1,
        competitorRank: competitorRank + 1,
        opportunity: yourRank === -1 ? 'missing' : 'underperforming'
      });
    }
  }

  return gaps;
}

Testing

Unit Tests with Jest

// serp-client.test.js
const { SERPpostClient } = require('./serp-client');
const axios = require('axios');

jest.mock('axios');

describe('SERPpostClient', () => {
  let client;

  beforeEach(() => {
    client = new SERPpostClient('test-api-key');
  });

  test('should perform Google search', async () => {
    const mockResponse = {
      data: {
        organic_results: [
          { title: 'Test Result', link: 'https://example.com' }
        ]
      }
    };

    axios.create.mockReturnValue({
      post: jest.fn().mockResolvedValue(mockResponse)
    });

    const results = await client.googleSearch('test query');
    expect(results.organic_results).toHaveLength(1);
  });
});

Deployment

Environment Variables

# .env
SERPPOST_API_KEY=your_api_key_here
NODE_ENV=production
PORT=3000
REDIS_URL=redis://localhost:6379

Docker Deployment

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

CMD ["node", "server.js"]

Best Practices

  1. Use environment variables for API keys
  2. Implement caching to reduce API calls
  3. Add rate limiting to prevent abuse
  4. Handle errors gracefully with retry logic
  5. Monitor performance with logging and metrics
  6. Use TypeScript for type safety
  7. Test thoroughly with unit and integration tests

Comparing Solutions

When choosing a SERP API for Node.js:

Next Steps

Conclusion

Node.js provides excellent tools for building scalable SERP API applications. Whether you’re using Express, Next.js, or serverless functions, SERPpost’s API integrates seamlessly with your Node.js stack.

Start building today with 100 free credits and see how easy SERP API integration can be.


Need help? Check our documentation or contact support.

Share:

Tags:

#SERP API #Node.js #JavaScript #Express #Next.js

Ready to try SERPpost?

Get started with 100 free credits. No credit card required.