Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Neko Singa — API

Backend API for the Neko Singa portfolio project. Built as a direct response to the Full-Stack Engineer (Frontend-Leaning) role at Elfa AI — the goal wasn't just to show I can code, but to show stack alignment and decision-making that matches what the role actually needs.

Live: api-nekosinga.vercel.app


Why This Project Is Built the Way It Is

The job posting calls out several specifics, and every technical decision in this repo is aimed at demonstrating those:

Requirement from the Job Posting How It's Addressed Here
"Node/TypeScript backend experience: Express, REST APIs, Postgres (we use Kysely), Redis, queues" This backend uses Express + TypeScript, Postgres via Neon, and Kysely as the query builder — the exact stack they mention
"Appreciation for financial markets & trading" Real crypto data integration (trending tokens, sentiment, market news) via the Elfa SDK, not mock data
"Work AI-first" Originally designed to use Elfa SDK's AI Chat feature as part of the product flow (see note below)
"Own features end-to-end, from UX/interaction design through frontend, API, and release" This repo is the backend half of a polyrepo system (web, app, api, docs) built and deployed from scratch to live

Queues (BullMQ/RabbitMQ in their production stack) are replaced here with Upstash QStash, since the entire backend is deployed as serverless functions on Vercel — BullMQ/RabbitMQ need a long-running process, which doesn't fit the serverless model. This is a deliberate architectural adaptation, not unfamiliarity with the concept.


A Real Constraint: Elfa Free Plan

This section is intentionally written transparently, because I think this part of the process is worth showing, not hiding.

The original plan was to build /api/agent/chat using elfa.chat() from @elfa-ai/sdk — Elfa's built-in AI Chat feature. On testing, the request came back with:

{
  "error": "ERR_FORBIDDEN",
  "message": "The AI Chat (Ask Elfa) endpoint requires a Grow or Pay-as-you-go plan."
}

That feature (along with Trending Narratives) turned out to be gated behind the Grow plan ($290/mo), not included in the Free tier I was using for development/testing.

The Decision I Made

Rather than ship a feature I couldn't actually test end-to-end — and risk it being unreliable for anyone trying to demo this — I chose to:

  1. Focus on what's available on the Free tier and make sure it works solidly: trending tokens, keyword mentions, token news, trending contract addresses, and account smart stats.
  2. Fix the error handling — the 403 from Elfa was originally collapsing into a generic 500 Internal Server Error on my side. I fixed this so Elfa's actual status code and message get forwarded, instead of being masked.
  3. Document the scope in the PRD (/docs), including which features were intentionally excluded and why, so there's no confusion for anyone reviewing this later.

AI Chat and Trending Narratives remain planned as a v2 milestone — the architecture (routes, request/response types, integration point) is already scaffolded, ready to enable if the plan gets upgraded.


Deployment Postmortem: Getting api-nekosinga.vercel.app Actually Running

This is a straight account of what went wrong shipping this API to Vercel and how each issue was tracked down, because debugging a "successful" deployment that serves nothing useful is its own skill worth documenting.

Symptom 1 — Every route returned 404: NOT_FOUND

The deployment showed a green "Ready" status in the Vercel dashboard, but every single route — including / — returned Vercel's generic 404: NOT_FOUND page. A "Ready" deployment with zero working routes is misleading: it means the build step didn't error out, not that the app actually built correctly.

Investigation: Compared this project's vercel.json against a known-working sibling project. This repo mixed the legacy builds + routes schema with the modern buildCommand / outputDirectory keys in the same file — a combination Vercel does not reliably support, and a strong early suspect. That pointed at config, but the real answer came from the build logs, not the config diff (see next issue).

Symptom 2 — Build silently failing: typescript@^7.0.2

Pulling the actual Build Logs (not just the deployment status) surfaced the real problem:

Using TypeScript 7.0.2 (local user-provided)
Error: Cannot read properties of undefined (reading 'readFile')

package.json had "typescript": "^7.0.2" — which resolves to TypeScript's native (Go-based) preview compiler, not a stable release. That preview has a different internal API from TS 5.x, and tsc crashed outright instead of compiling. This explains Symptom 1: the build never produced valid output, so there was nothing for Vercel to route to.

Fix: Pinned typescript to a stable 5.x release and reinstalled:

- "typescript": "^7.0.2"
+ "typescript": "^5.7.3"

Symptom 3 — Build succeeds, function crashes at runtime: ERR_REQUIRE_ESM

With the build fixed, the function was deployed and actually got invoked — but every request now failed with FUNCTION_INVOCATION_FAILED (500). The Runtime Logs showed:

Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/node_modules/kysely/dist/index.js
from /var/task/src/db/client.js not supported.
Instead change the require of index.js in .../src/db/client.js to a dynamic import()
which is available in all CommonJS modules.

Root cause: kysely ships as a pure ESM package (no CommonJS build). This project's tsconfig.json had "module": "commonjs", so every import compiled down to require() — which Node refuses for ESM-only packages.

Fix: Migrated the project to ESM instead of trying to force an ESM package to work under CommonJS:

# package.json
+ "type": "module"

# tsconfig.json
- "module": "commonjs",
- "moduleResolution": "bundler",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",

Every relative import in src/**/*.ts also needed an explicit .js extension, which NodeNext module resolution requires:

- import { db } from './db/client';
+ import { db } from './db/client.js';

Outcome

Layer Symptom Root Cause Fix
Routing 404 on every path Misleading — see Build below N/A, resolved once the build actually succeeded
Build Deployment "Ready" but produced nothing typescript@^7.0.2 (native preview) crashing tsc Pinned to stable typescript@^5.x
Runtime 500 FUNCTION_INVOCATION_FAILED kysely is ESM-only; project compiled to CommonJS Migrated project to ESM (NodeNext + .js import extensions)

Takeaway: A "successful" deployment status only means the platform didn't detect an error — it doesn't mean the app works. Build Logs and Runtime Logs are two different failure surfaces and need to be checked separately; the fix for a 404 can live entirely outside routing config.


Available Endpoints (Free Tier)

Method Endpoint Data Source
GET /api/health
GET /api/market/trending elfa.getTrendingTokens
GET /api/market/sentiment/:token elfa.getKeywordMentions
GET /api/market/news elfa.getTokenNews
GET /api/market/trending-cas elfa.getTrendingCAsTwitter
GET /api/market/stats/:username elfa.getAccountSmartStats
POST /api/auth/login

Tech Stack

  • Runtime: Node.js + TypeScript (ESM)
  • Framework: Express
  • Database: PostgreSQL (Neon) + Kysely
  • Cache: Redis (Upstash)
  • Queue: Upstash QStash (serverless-friendly alternative to BullMQ)
  • Auth: JWT
  • Data Source: @elfa-ai/sdk
  • Deploy: Vercel (Serverless Functions)

Related Repos

Part of the nekosinga polyrepo:

  • web — landing page
  • app — main dashboard/product
  • docs — documentation

About

REST API for Neko Singa — Express, Postgres (Kysely), and Elfa SDK integration for crypto market data.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages