A minimal, reproducible setup to call curl_cffi from Node.js using gRPC.
- requester: Python 3.11 service that exposes a gRPC API and performs HTTP requests with
curl_cffi. - worker: Node.js example client that calls the Python service and logs results.
Everything is wired with docker-compose. Protobufs are compiled during image builds.
- The Node worker calls the Python requester over gRPC using the
HttpProxy.SendRequestmethod defined inprotos/service.proto. - The example request fetches
https://pokeapi.co/api/v2/pokemon/dittothrough the Python service usingcurl_cffi. - You’ll see a log line like
Found dittoif everything is wired correctly.
- Docker and docker-compose
- No local Python or Node setup required
You can use defaults, or copy the example env files to tweak:
cp requester/.env.example requester/.env
cp worker/.env.example worker/.envKey vars:
-
requester/.env
IMPERSONATEuser agent profile forcurl_cffi(e.g.chrome,edge)GRPC_PORTdefault50051LOG_LEVELINFOorWARNMAX_WORKERSgRPC server thread poolREQUEST_TIMEOUTseconds
-
worker/.env
GRPC_SERVER_HOSTshould berequester(the docker-compose service name)GRPC_SERVER_PORTdefault50051
docker compose up --buildThis uses docker-compose.yml:
- Builds both images
- Generates protobuf stubs in both containers during build
- Runs the worker once, which performs the example request and logs the result
docker compose -f docker-compose.dev.yml up --buildDev compose:
- Sets
NODE_ENV=development - Mounts
./worker/srcread-only into the container for instant updates withtsx watch
You should see logs from the worker like:
info: Sending example request...
info: Found ditto
info: Done
If you don’t, skip down to Troubleshooting and pretend you read this whole README first.
Edit worker/src/utils/requests.ts and adjust the base URL:
const response = await grpcClient.sendRequest({
url: `https://your-api.example.com/${path}`,
method: opts?.method ?? 'GET',
headers: { 'cookie': 'exampleCookie=exampleValue;' },
params: opts?.query,
body: JSON.stringify(opts?.data) || '',
});Create a new service function (or copy the included one):
import { sendApiRequest } from '@/utils/requests';
export const getThing = async (id: string) => {
const res = await sendApiRequest<{ id: string; name: string }>(`things/${id}`);
if (!res.ok) return;
console.log(res.data.name);
};Call it from src/main.ts.
-
Worker can’t connect to gRPC
- Ensure
GRPC_SERVER_HOST=requesterinworker/.env. - Confirm the
requestercontainer is healthy and started beforeworker(composedepends_onis set).
- Ensure
-
Requests fail or time out
- Increase
REQUEST_TIMEOUTinrequester/.env. - Check
IMPERSONATEvalue; trychrome,edge, or a different supported profile.
- Increase
-
Proto mismatch
-
After editing
protos/service.proto, rebuild images:docker compose build --no-cache docker compose up
-
-
Need logs
- Use dev compose (
docker-compose.dev.yml) for more verbose logs. - You can override log levels via env:
LOG_LEVEL=INFOon requester,NODE_ENV=developmenton worker.
- Use dev compose (
MIT.