Skip to main content

Search API

Search API Documentation

Author: Josh S. Sakweli, Backend Lead Team
Last Updated: 2025-12-23
Version: v1.0

Base URL: https://api.nextgate.com/api/v1

Short Description: Public search endpoints for discovering users and shops on the Nexgate platform. These endpoints power the @mention and $shop autocomplete features across the application.

Hints:

  • Use @ prefix for user search (automatically stripped)
  • Use $ prefix for shop search (automatically stripped)
  • Results are ordered by relevance: exact match → starts with → contains
  • All endpoints are public and do not require authentication
  • Rate limiting applies to prevent abuse

Standard Response Format

All API responses follow a consistent structure using our Globe Response Builder pattern:

Success Response Structure

{
  "success": true,
  "httpStatus": "OK",
  "message": "Operation completed successfully",
  "action_time": "2025-12-23T10:30:45",
  "data": {
    // Actual response data goes here
  }
}

Error Response Structure

{
  "success": false,
  "httpStatus": "BAD_REQUEST",
  "message": "Error description",
  "action_time": "2025-12-23T10:30:45",
  "data": "Error description"
}

Endpoints

1. Search Users

Purpose: Search for users by username, first name, or last name for @mentions and user discovery.

Endpoint: GET {base_url}/users/search

Access Level: 🌐 Public (Rate limited)

Authentication: None

Query Parameters:

Parameter Type Required Description Validation Default
q string No Search query (username, first name, or last name). @ prefix is automatically removed Min: 1 character after trimming -
page integer No Page number (1-based) Min: 1 1
size integer No Number of results per page Min: 1, Max: 50 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Users retrieved successfully",
  "action_time": "2025-12-23T11:14:05.293524449",
  "data": {
    "content": [
      {
        "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "userName": "john_doe",
        "displayName": "John Doe",
        "avatarUrl": "https://cdn.nextgate.com/avatars/john_doe.jpg",
        "isVerified": true
      },
      {
        "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
        "userName": "johnny_b",
        "displayName": "Johnny Brown",
        "avatarUrl": null,
        "isVerified": false
      }
    ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10,
      "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
      },
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "last": false,
    "totalPages": 3,
    "totalElements": 25,
    "first": true,
    "size": 10,
    "number": 0,
    "numberOfElements": 10,
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "empty": false
  }
}

Success Response Fields:

Field Description
content Array of user objects matching the search query
content[].id Unique identifier of the user
content[].userName Username of the user
content[].displayName Full display name (firstName + lastName, or userName if not available)
content[].avatarUrl URL to user's profile picture (null if not set)
content[].isVerified Whether the user account is verified
totalElements Total number of users matching the query
totalPages Total number of pages available
first Whether this is the first page
last Whether this is the last page

Empty Result Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Users retrieved successfully",
  "action_time": "2025-12-23T11:14:05.293524449",
  "data": {
    "content": [],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10,
      "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
      },
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "last": true,
    "totalPages": 0,
    "totalElements": 0,
    "first": true,
    "size": 10,
    "number": 0,
    "numberOfElements": 0,
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "empty": true
  }
}

Example Requests:

Use Case Request
Basic search /users/search?q=john
Search with @ prefix /users/search?q=@john
Paginated search /users/search?q=john&page=2&size=20

2. Search Shops

Purpose: Search for shops by name, slug, or tagline for $mentions and shop discovery.

Endpoint: GET {base_url}/shops/search

Access Level: 🌐 Public (Rate limited)

Authentication: None

Query Parameters:

Parameter Type Required Description Validation Default
q string No Search query (shop name, slug, or tagline). $ prefix is automatically removed Min: 1 character after trimming -
page integer No Page number (1-based) Min: 1 1
size integer No Number of results per page Min: 1, Max: 50 10

Success Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Shops retrieved successfully",
  "action_time": "2025-12-23T11:20:30.123456789",
  "data": {
    "content": [
      {
        "shopId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
        "shopName": "Mama's Kitchen",
        "shopSlug": "mamas-kitchen",
        "tagline": "Best local food in town",
        "logoUrl": "https://cdn.nextgate.com/shops/mamas-kitchen/logo.jpg",
        "isVerified": true,
        "verificationBadge": "GOLD",
        "city": "Dar es Salaam"
      },
      {
        "shopId": "d4e5f6a7-b8c9-0123-def0-234567890123",
        "shopName": "Mama Lishe",
        "shopSlug": "mama-lishe",
        "tagline": "Authentic Tanzanian cuisine",
        "logoUrl": null,
        "isVerified": false,
        "verificationBadge": null,
        "city": "Arusha"
      }
    ],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10,
      "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
      },
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "last": false,
    "totalPages": 2,
    "totalElements": 15,
    "first": true,
    "size": 10,
    "number": 0,
    "numberOfElements": 10,
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "empty": false
  }
}

Success Response Fields:

Field Description
content Array of shop objects matching the search query
content[].shopId Unique identifier of the shop
content[].shopName Display name of the shop
content[].shopSlug URL-friendly unique identifier
content[].tagline Short promotional text for the shop
content[].logoUrl URL to shop's logo image (null if not set)
content[].isVerified Whether the shop is verified
content[].verificationBadge Verification badge level (GOLD, SILVER, BRONZE, or null)
content[].city City where the shop is located
totalElements Total number of shops matching the query
totalPages Total number of pages available
first Whether this is the first page
last Whether this is the last page

Empty Result Response JSON Sample:

{
  "success": true,
  "httpStatus": "OK",
  "message": "Shops retrieved successfully",
  "action_time": "2025-12-23T11:20:30.123456789",
  "data": {
    "content": [],
    "pageable": {
      "pageNumber": 0,
      "pageSize": 10,
      "sort": {
        "empty": true,
        "sorted": false,
        "unsorted": true
      },
      "offset": 0,
      "paged": true,
      "unpaged": false
    },
    "last": true,
    "totalPages": 0,
    "totalElements": 0,
    "first": true,
    "size": 10,
    "number": 0,
    "numberOfElements": 0,
    "sort": {
      "empty": true,
      "sorted": false,
      "unsorted": true
    },
    "empty": true
  }
}

Example Requests:

Use Case Request
Basic search /shops/search?q=mama
Search with $ prefix /shops/search?q=$mama
Paginated search /shops/search?q=kitchen&page=2&size=20

Search Ordering Logic

Both search endpoints use smart ordering to return the most relevant results first:

Priority Match Type Example (query: "john")
1 Exact match john
2 Starts with johnny, john_doe
3 Contains big_john, _john_

Results with the same priority are sorted alphabetically.


Error Responses

Standard Error Types:

Application-Level Exceptions (400-499)

  • 400 BAD_REQUEST: Invalid request parameters
  • 429 TOO_MANY_REQUESTS: Rate limit exceeded

Error Response Examples:

Rate Limit Exceeded (429):

{
  "success": false,
  "httpStatus": "TOO_MANY_REQUESTS",
  "message": "Rate limit exceeded. Please try again later.",
  "action_time": "2025-12-23T11:30:45",
  "data": "Rate limit exceeded. Please try again later."
}

Quick Reference

Feature User Search Shop Search
Endpoint /users/search /shops/search
Prefix @ (optional) $ (optional)
Searches userName, firstName, lastName shopName, shopSlug, tagline
Filters Non-locked users only Active, non-deleted shops only
Auth None None

Future Scaling Strategy

As Nexgate grows, the search system will evolve to handle millions of users and shops while maintaining fast response times. Here's how we'll scale like Instagram and GitHub.


Phase 1: Current Implementation (MVP)

Scale: Up to 100K users/shops

How it works:

  • Direct PostgreSQL queries with pattern matching
  • Smart ordering: exact match → starts with → contains
  • Simple and effective for early-stage growth

Limitations:

  • Slows down with large datasets
  • No typo tolerance
  • No personalization

Phase 2: Database Optimization

Scale: 100K - 1M users/shops

What we'll add:

  • Trigram Indexing (pg_trgm): PostgreSQL extension that enables fast fuzzy search and handles typos (e.g., "jonh" finds "john")
  • Redis Caching: Cache popular search queries and recent searches per user
  • Optimized Indexes: GIN indexes for faster text matching

Benefits:

  • 3-5x faster queries
  • Typo tolerance
  • Reduced database load

Phase 3: Dedicated Search Engine

Scale: 1M - 100M users/shops

What we'll add:

  • Elasticsearch Cluster: Dedicated search infrastructure separate from main database
  • Real-time Sync: Changes in PostgreSQL automatically sync to Elasticsearch
  • Advanced Features: Autocomplete suggestions, fuzzy matching, relevance scoring

Benefits:

  • Sub-10ms response times
  • Horizontal scaling (add more nodes as needed)
  • Rich search features without impacting main database

Phase 4: Personalized Search (Instagram/GitHub Style)

Scale: 100M+ users/shops

How Instagram does it: They don't search all 2 billion users. Instead, they search within YOUR world first.

Tiered Search Approach:

PriorityWho Gets Searched FirstWhy
1stPeople you message frequentlyStrongest signal of relationship
2ndPeople you interact with (likes, comments)Active engagement
3rdPeople you followExplicit interest
4thPeople who follow youThey know you
5thGlobal searchOnly if above tiers don't have enough results

What we'll add:

  • Interaction Graph: Track who talks to whom, who views whose profile, who likes whose posts
  • Pre-computed Candidates: For each user, maintain a list of likely search targets
  • ML Ranking: Machine learning model to predict who you're most likely searching for
  • Prefix Caching: Pre-compute results for common prefixes (typing "j" instantly shows your friend "John")

Relevance Scoring:

  • 40% - How often you interact with this person/shop
  • 30% - How well the query matches their name
  • 20% - How recently you interacted
  • 10% - Their popularity (followers, verification)

Performance Targets

PhaseScaleAverage Response Time
Phase 1 (Current)<100K~50ms
Phase 2 (Optimized DB)<1M~30ms
Phase 3 (Elasticsearch)<100M~10ms
Phase 4 (Personalized)100M+~5ms

Migration Triggers

When to MoveAction
50K usersAdd pg_trgm + Redis caching
500K usersIntroduce Elasticsearch
5M usersBuild interaction graph + personalization

Key Principle: The API stays the same. Users won't notice any changes except faster, smarter results.