Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

LiteAPKS API

A production-ready REST API that scrapes liteapks.com using Playwright + a stealth plugin (to work around Cloudflare/anti-bot protection), and exposes clean, standardized JSON endpoints for consumption by any client — including a Laravel backend.

Built with a single, reused browser instance, response caching, rate limiting, and a modular architecture designed so additional scraper sources (APKPure, APKMody, APKDone, HappyMod, etc.) can be added later without touching existing code.


Table of Contents


Features

  • ✅ Search LiteAPKS by keyword
  • ✅ Full app detail scraping (description, screenshots, mod info, download link)
  • ✅ Cloudflare bypass via playwright-extra + puppeteer-extra-plugin-stealth
  • Single reused browser instance — no per-request browser launches
  • ✅ Automatic browser relaunch on crash/disconnect
  • ✅ In-memory caching (NodeCache) with separate TTLs for search vs. details
  • ✅ Standardized JSON envelope on every response, success or failure
  • ✅ API key authentication on all /api/v1/* routes
  • ✅ Retry-once logic on navigation/scrape failures
  • ✅ Helmet, CORS, Compression, hidden X-Powered-By
  • ✅ Rate limiting (100 requests / 15 minutes per IP)
  • ✅ Morgan HTTP access logs + structured console logger
  • ✅ Docker-ready (Microsoft Playwright base image)
  • ✅ Render-ready (render.yaml included)
  • ✅ ES Modules throughout, modern async/await, JSDoc comments

Folder Structure

liteapks-api/
├── src/
│   ├── app.js                       # Express app assembly (middleware, routes)
│   ├── server.js                    # Entrypoint: boots browser then HTTP server
│   ├── browser/
│   │   └── browser.js               # BrowserManager singleton (launch/reuse/relaunch)
│   ├── routes/
│   │   ├── health.js                 # GET /health
│   │   └── liteapks.js               # GET /api/v1/liteapks/*
│   ├── controllers/
│   │   └── liteapks.controller.js    # HTTP request/response shaping
│   ├── services/
│   │   ├── liteapks.service.js       # Business logic: cache-aware orchestration
│   │   └── cache.service.js          # NodeCache wrapper
│   ├── scraper/
│   │   └── liteapks.scraper.js       # Raw Playwright scraping logic + retry
│   ├── middleware/
│   │   ├── errors.js                  # 404 handler + centralized error handler
│   │   └── rateLimiter.js             # express-rate-limit configuration
│   └── utils/
│       └── logger.js                  # Timestamped console logger
├── package.json
├── Dockerfile
├── render.yaml
├── .env.example
└── .gitignore

Environment Variables

Variable Default Description
PORT 3000 Port the HTTP server listens on
NODE_ENV development development or production
CORS_ORIGIN * Allowed CORS origin (single origin or *)
API_KEY (required) Secret API key required to access protected routes

Copy .env.example to .env and adjust as needed:

cp .env.example .env

Example .env:

PORT=3000
NODE_ENV=development
CORS_ORIGIN=*
API_KEY=your-super-secret-api-key

Authentication

All API endpoints under:

/api/v1/liteapks/*

are protected using an API Key. The public / and /health endpoints do not require authentication.

Sending the key

Every protected request must include the following HTTP header:

x-api-key: YOUR_API_KEY

The API key is configured through the API_KEY environment variable:

API_KEY=your-super-secret-api-key

Error responses

Scenario HTTP Status Message
Missing x-api-key header 401 API key is required.
Invalid/incorrect API key 403 Invalid API key.

Security note

  • Never commit your real API_KEY to version control — keep it only in .env (which is git-ignored) or in your host's environment/secret manager.
  • Rotate the key if it's ever exposed (e.g. committed by mistake, leaked in logs, or shared in a support channel).
  • On Render, set API_KEY as an environment variable in the service's Environment tab (or via render.yaml with sync: false) rather than hardcoding it anywhere in the repo.

Installation

Requires Node.js 22+.

git clone <your-repo-url> liteapks-api
cd liteapks-api
npm install

npm install runs a postinstall script (playwright install --with-deps chromium) that downloads the Chromium binary Playwright needs. This can take a minute the first time.


Development

npm run dev

This uses Node's built-in --watch flag to restart the server on file changes. The server starts on http://localhost:3000 (or PORT from your .env).


Production

npm install
npm start

For production deployments, set NODE_ENV=production — this switches Morgan to the combined log format and suppresses debug-level logs and stack traces in error responses.


Docker Deployment

The included Dockerfile is based on Microsoft's official Playwright image (mcr.microsoft.com/playwright), which already bundles every OS-level dependency Chromium needs (fonts, codecs, sandboxing libraries) — this avoids the common "missing shared library" failures you get with a plain node base image.

Build and run:

docker build -t liteapks-api .
docker run -p 3000:3000 --env-file .env liteapks-api

The container runs as the non-root pwuser user (provided by the base image) and exposes a Docker HEALTHCHECK that hits /health every 30 seconds.


Render Deployment

This repo includes a render.yaml for one-click Render deployment using the "Blueprint" flow:

  1. Push this repository to GitHub.
  2. In Render, choose New > Blueprint and point it at your repo.
  3. Render reads render.yaml, builds the Docker image, and deploys automatically.
  4. Render's health checks hit GET /health, which reports whether the underlying browser is currently connected.

render.yaml sets PORT, NODE_ENV=production, and CORS_ORIGIN=* as environment variables — adjust CORS_ORIGIN to your Laravel app's domain in production for tighter security.

You must also configure API_KEY as an environment variable on the Render service (Dashboard → your service → Environment → Add Environment Variable, or via render.yaml using sync: false so the value isn't stored in the repo). Without it set, protected /api/v1/liteapks/* routes will reject all requests.

If you'd rather deploy without Docker (Render's native Node runtime), set:

  • Build Command: npm install
  • Start Command: npm start

Render's native Node environment will run the postinstall script automatically, downloading Chromium during the build step.


API Reference

All responses use this envelope:

{
  "success": true,
  "cached": false,
  "data": { }
}

Errors always look like:

{
  "success": false,
  "message": "Human readable explanation"
}

Authentication: Endpoints under /api/v1/liteapks/* require the x-api-key header (see Authentication). GET / and GET /health are public and do not require an API key.

GET / — public

Basic service identity check.

{
  "success": true,
  "name": "LiteAPKS API",
  "version": "1.0.0"
}

GET /health — public

Reports process + browser health. Used by Docker/Render health checks.

{
  "success": true,
  "browser": "connected",
  "uptime": 1234,
  "memory": "128 MB"
}

GET /api/v1/liteapks/search?q=whatsapp — requires API key

Searches LiteAPKS for a keyword.

Query params

Param Required Description
q yes Search keyword

Response

{
  "success": true,
  "cached": false,
  "count": 10,
  "data": [
    {
      "id": "whatsapp-messenger",
      "title": "WhatsApp Messenger MOD APK",
      "clean_title": "WhatsApp Messenger",
      "url": "https://liteapks.com/whatsapp-messenger.html",
      "image": "https://liteapks.com/wp-content/uploads/whatsapp.png",
      "version": "2.24.15.75",
      "size": "45 MB",
      "mod_info": "Unlocked Premium Features",
      "author": "LiteAPKS",
      "is_mod": true
    }
  ]
}

Cached for 10 minutes per keyword.

GET /api/v1/liteapks/details/:slug — requires API key

Fetches full details for a single app.

Response

{
  "success": true,
  "cached": false,
  "data": {
    "title": "WhatsApp Messenger v2.24.15.75 MOD APK",
    "clean_title": "WhatsApp Messenger",
    "version": "2.24.15.75",
    "size": "45 MB",
    "developer": "WhatsApp LLC",
    "package_name": "com.whatsapp",
    "category": "Communication",
    "image": "https://liteapks.com/wp-content/uploads/whatsapp.png",
    "screenshots": [
      "https://liteapks.com/wp-content/uploads/ss1.png",
      "https://liteapks.com/wp-content/uploads/ss2.png"
    ],
    "description": "WhatsApp Messenger is a free messaging app...",
    "description_html": "<p>WhatsApp Messenger is a free messaging app...</p>",
    "mod_info": ["Unlocked Premium Features", "No Ads"],
    "mod_info_text": "Unlocked Premium Features, No Ads",
    "download_url": "https://liteapks.com/download/whatsapp-messenger"
  }
}

Cached for 30 minutes per slug.

Error Cases Handled

Scenario HTTP Status Message
Missing x-api-key header 401 API key is required.
Invalid/incorrect API key 403 Invalid API key.
Missing q param 400 Query parameter 'q' is required
Invalid slug format 400 Invalid slug format
App not found (404 from source) 404 The requested app was not found.
Navigation timeout 504 The request to LiteAPKS timed out. Please try again.
Cloudflare / anti-bot block 503 LiteAPKS is currently blocking automated access...
Browser/network failure 502 Upstream browser/network error. Please retry.
Rate limit exceeded 429 Too many requests. Please try again later.
Unexpected error 500 Internal server error.

Curl Examples

# Root (public — no API key needed)
curl https://your-app.onrender.com/

# Health check (public — no API key needed)
curl https://your-app.onrender.com/health

# Search (requires API key)
curl -H "x-api-key: YOUR_API_KEY" \
  "https://your-app.onrender.com/api/v1/liteapks/search?q=whatsapp"

# Details (requires API key)
curl -H "x-api-key: YOUR_API_KEY" \
  "https://your-app.onrender.com/api/v1/liteapks/details/whatsapp-messenger"

Laravel Integration

Since this API returns a standardized JSON envelope, integrating it into a Laravel app (e.g. the admin panel app-directory scraper mentioned in your project) is straightforward using Laravel's HTTP client.

<?php
// app/Services/LiteApksClient.php

namespace App\Services;

use Illuminate\Support\Facades\Http;

class LiteApksClient
{
    protected string $baseUrl;
    protected string $apiKey;

    public function __construct()
    {
        $this->baseUrl = config('services.liteapks.base_url', 'https://your-app.onrender.com');
        $this->apiKey  = config('services.liteapks.api_key');
    }

    public function search(string $keyword): array
    {
        $response = Http::timeout(60)
            ->withHeaders(['x-api-key' => $this->apiKey])
            ->get("{$this->baseUrl}/api/v1/liteapks/search", ['q' => $keyword]);

        $response->throw();

        return $response->json('data', []);
    }

    public function details(string $slug): array
    {
        $response = Http::timeout(60)
            ->withHeaders(['x-api-key' => $this->apiKey])
            ->get("{$this->baseUrl}/api/v1/liteapks/details/{$slug}");

        $response->throw();

        return $response->json('data', []);
    }
}

Register the base URL and API key in config/services.php:

'liteapks' => [
    'base_url' => env('LITEAPKS_API_URL', 'https://your-app.onrender.com'),
    'api_key'  => env('LITEAPKS_API_KEY'),
],

And in .env:

LITEAPKS_API_URL=https://your-app.onrender.com
LITEAPKS_API_KEY=your-super-secret-api-key

Usage in a controller or job:

$client = new \App\Services\LiteApksClient();
$results = $client->search('whatsapp');
$details = $client->details($results[0]['id']);

Because every response follows the { success, cached, data } envelope, your Laravel Http::throw() calls will raise on non-2xx statuses, and success: false bodies (400/401/403/404/503/etc.) carry a human-readable message you can log or surface to an admin UI.


Architecture Notes

Browser reuse & auto-restart

src/browser/browser.js exports a BrowserManager singleton that:

  1. Launches Chromium once, when the server boots (server.js awaits browserManager.launch() before calling app.listen()).
  2. Keeps one BrowserContext alive for the life of the process.
  3. Opens a new page per request (getPage()), never a new browser.
  4. Listens for the browser's disconnected event; if the browser crashes, the next getPage() call transparently relaunches it.

Caching

src/services/cache.service.js wraps NodeCache behind a small interface (get, set, del, flush, stats) so it can be swapped for Redis later without touching any controller or service code. TTLs are enforced at the call site in liteapks.service.js:

  • Search results: 10 minutes (search:<keyword>)
  • App details: 30 minutes (details:<slug>)

Retry logic

src/scraper/liteapks.scraper.js's withRetry() helper wraps every scrape: on failure, it closes the failed page, opens a fresh one, and retries once before propagating the error up to the centralized error handler.

Error handling

src/middleware/errors.js classifies thrown errors (timeout, Cloudflare block, not-found, browser/network failure, or generic) into the right HTTP status code and a friendly message — the process never crashes on a scraping failure.


Extending With New Scraper Sources

The architecture is intentionally source-agnostic. To add a new source (e.g. APKPure), you would add — without modifying any existing file:

src/scraper/apkpure.scraper.js       # raw Playwright scraping logic
src/services/apkpure.service.js      # cache-aware orchestration
src/controllers/apkpure.controller.js
src/routes/apkpure.js

Then mount the new route in app.js:

import apkpureRoutes from "./routes/apkpure.js";
app.use("/api/v1/apkpure", apkpureRoutes);

Because BrowserManager is a shared singleton, every new scraper source reuses the same browser instance — no additional Chromium processes are spawned per source.

About

A production-ready REST API that scrapes liteapks.com using Playwright + a stealth plugin (to work around Cloudflare/anti-bot protection), and exposes clean, standardized JSON endpoints for consumption by any client — including a Laravel backend.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages