Skip to main content
Complete reference for building and deploying applications on Hiveku’s serverless infrastructure.
Platform: AWS Lambda + CloudFront + S3 | Runtime: Node.js 20.x on ARM64 | Not supported: PHP, WebSockets, native modules without layers, persistent processes

Architecture

  • Static assets are built and uploaded to S3, served via CloudFront CDN (global edge locations)
  • Automatic HTTPS with wildcard certificate
  • URL rewriting for clean routes (/about/about/index.html)
  • Cache: HTML 1 min, CSS/JS 5 min
  • Each API route becomes a Lambda function with its own URL
  • SSR pages are bundled with the server runtime

Resource Limits

ResourceDefaultMaximumNotes
Memory256 MB10,240 MBHigher memory = faster CPU
Timeout30 seconds900 seconds (15 min)SSR uses 60s default
Bundle Size (zipped)-50 MBLarger uses S3 upload
Bundle Size (unzipped)-250 MBAWS Lambda hard limit
Request Payload-6 MBUse S3 presigned URLs for larger
Response Payload-6 MBStream large responses
Ephemeral Storage (/tmp)512 MB10,240 MBCleared between invocations
Environment Variables-4 KB totalUse Secrets Manager for large
Concurrent Executions1,000Depends on quotaPer-account AWS limit
ZIP Import (upload)-1 GBMax 10,000 files

Do’s and Don’ts

  • Use pure JavaScript packagesbcryptjs, pg (pure mode), jose for JWT
  • Keep functions small — split large APIs into separate files for faster cold starts
  • Use environment variables — store secrets in Hiveku’s env vars, not in code
  • Optimize bundle size — use dynamic imports, tree-shaking, avoid bundling dev deps
  • Use S3 for large files — generate presigned URLs for uploads over 6MB
  • Return proper status codes — use 200, 201, 400, 401, 404, 500 appropriately
  • Set cache headers — use Cache-Control for static responses
  • Handle cold starts — initialize DB connections outside the handler

API Routes

Where to Place API Files

Hiveku auto-detects API routes from these directories:
# Standard locations
api/
src/api/
functions/
lambdas/
serverless/

# Next.js App Router
app/api/**/route.ts

# Next.js Pages Router
pages/api/**/*.ts

File to URL Mapping

File PathDeployed URL
api/users.ts/api/users
api/users/[id].ts/api/users/:id
api/files/[...path].ts/api/files/* (catch-all)
app/api/auth/route.ts/api/auth

API Handler Formats

Hiveku supports two function formats — use whichever you prefer:
FeatureWeb API FormatAWS Lambda Format
Exportexport default functionexport async function handler
HTTP Methodreq.methodevent.httpMethod
Query Paramsreq.query.idevent.queryStringParameters?.id
Request Bodyreq.body (auto-parsed)JSON.parse(event.body)
ResponseResponse.json(data){ statusCode, body, headers }

hiveku.json Configuration

Create a hiveku.json in your project root to customize deployment:
{
  "functions": {
    "directories": ["api/", "src/lambdas/"],
    "include": ["lib/scheduled-job.ts"],
    "exclude": ["api/internal/**", "**/*.test.ts"],
    "routes": {
      "api/legacy-auth.ts": "/api/v1/auth",
      "api/new-auth.ts": "/api/v2/auth"
    },
    "config": {
      "api/image-processor.ts": {
        "memory": 1024,
        "timeout": 120
      },
      "api/heavy-compute.ts": {
        "memory": 2048,
        "timeout": 300
      },
      "**": {
        "memory": 256,
        "timeout": 30
      }
    }
  },
  "build": {
    "outputDirectory": "dist",
    "env": {
      "NEXT_PUBLIC_API_URL": "https://api.example.com"
    }
  }
}
FieldDescription
functions.directoriesAdditional directories to scan for API routes
functions.includeSpecific files to include as functions
functions.excludeGlob patterns to exclude
functions.routesOverride auto-generated URL paths
functions.configPer-function memory/timeout settings. Use ** for defaults
build.outputDirectoryCustom build output directory
build.envBuild-time environment variables (NEXT_PUBLIC_*)

Advanced Features

Lambda Layers

Auto-provisioned layers for native dependencies. These packages just work when you add the layer: sharp bcrypt argon2 canvas prisma ffmpeg puppeteer

Scheduled Functions (Cron)

Add a @schedule comment or put files in a cron/ directory:
// api/cron/daily-report.ts
// @schedule cron(0 9 * * ? *)

export default async function() {
  const report = await generateReport();
  await sendEmail(report);
  return { success: true };
}
Cron format: cron(min hour day-of-month month day-of-week year)
ExpressionDescription
cron(0 9 * * ? *)9 AM daily
cron(0 */2 * * ? *)Every 2 hours
cron(0 9 ? * MON-FRI *)9 AM weekdays
cron(0 0 1 * ? *)1st of month
Rate format: rate(5 minutes), rate(1 hour), rate(1 day), rate(7 days)

Streaming Responses

SSE and chunked responses for AI apps. Auto-detected from text/event-stream content type:
// api/stream/ai-chat.ts
// @streaming

export default async function(req: Request) {
  const { prompt } = await req.json();
  
  return new Response(
    new ReadableStream({
      async start(controller) {
        for await (const chunk of streamAI(prompt)) {
          controller.enqueue(`data: ${JSON.stringify(chunk)}\n\n`);
        }
        controller.close();
      }
    }),
    { headers: { 'Content-Type': 'text/event-stream' } }
  );
}

Package Compatibility

PackageUse For
prismaORM (use connection pooling + keep-alive for serverless)
pg / mysql2Database drivers (pure JS mode)
bcryptjsPassword hashing (pure JS)
joseJWT signing/verification
zodSchema validation
axios / node-fetchHTTP clients
@aws-sdk/*AWS services (S3, SES, etc.)
stripePayment processing
resend / nodemailerEmail sending
openaiAI/LLM integration

Code Patterns

Database Connection (Prisma)

Initialize the Prisma client outside the handler so connections are reused across Lambda invocations. Use connection pooling and keep-alive pings to avoid cold reconnect latency.
// lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
  connectionWarmed: boolean;
  keepAliveInterval: NodeJS.Timeout | undefined;
};

export const prisma =
  globalForPrisma.prisma ??
  new PrismaClient({
    log: process.env.NODE_ENV === 'development' ? ['error', 'warn'] : ['error'],
    datasources: {
      db: { url: process.env.DATABASE_URL },
    },
  });

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

// Pre-warm the connection on cold start (reduces first query latency)
if (!globalForPrisma.connectionWarmed) {
  globalForPrisma.connectionWarmed = true;
  prisma.$queryRaw`SELECT 1`.catch(() => {});
}

// Keep-alive: ping every 20s to prevent connection pool from closing
// Supabase session pooler closes idle connections after ~30 seconds
if (process.env.NODE_ENV === 'production' && !globalForPrisma.keepAliveInterval) {
  globalForPrisma.keepAliveInterval = setInterval(async () => {
    try { await prisma.$queryRaw`SELECT 1`; } catch {}
  }, 20000);
  globalForPrisma.keepAliveInterval.unref?.();
}

export default prisma;
Set connection_limit and pool_timeout in your DATABASE_URL query string. A limit of 10-15 connections with a 30s timeout works well for serverless.

JWT Authentication

Use the jose library for JWT — it’s pure JavaScript and works on Lambda. Avoid jsonwebtoken as it has native dependencies.
// lib/auth.ts
import { SignJWT, jwtVerify } from 'jose';

const secret = new TextEncoder().encode(process.env.JWT_SECRET);

export async function signToken(payload: { userId: string; email: string }) {
  return await new SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('7d')
    .sign(secret);
}

export async function verifyToken(token: string) {
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload as { userId: string; email: string };
  } catch {
    return null;
  }
}
// api/protected.ts - Protected route example
import { verifyToken } from '../lib/auth';

export default async function handler(req: Request) {
  // Headers are lowercase on Lambda!
  const authHeader = req.headers.get('authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return Response.json({ error: 'Missing token' }, { status: 401 });
  }
  
  const payload = await verifyToken(authHeader.slice(7));
  if (!payload) {
    return Response.json({ error: 'Invalid token' }, { status: 401 });
  }
  
  return Response.json({ message: 'Authenticated!', user: payload });
}

Large File Upload (S3 Presigned URL)

// api/upload-url.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const s3 = new S3Client({ region: process.env.AWS_REGION });

export default async function handler(req: Request) {
  const { filename, contentType } = await req.json();
  
  const command = new PutObjectCommand({
    Bucket: process.env.S3_BUCKET,
    Key: `uploads/${Date.now()}-${filename}`,
    ContentType: contentType,
  });
  
  const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
  return Response.json({ uploadUrl });
}

Custom CORS Headers

// api/data.ts
export default async function handler(req: Request) {
  const headers = {
    'Access-Control-Allow-Origin': 'https://myapp.com',
    'Access-Control-Allow-Methods': 'GET, POST',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  };
  
  if (req.method === 'OPTIONS') {
    return new Response(null, { status: 204, headers });
  }
  
  return Response.json({ data: 'hello' }, { headers });
}

Reusable Auth Middleware

// lib/withAuth.ts
import { verifyToken } from './auth';

type AuthenticatedHandler = (
  req: Request,
  user: { userId: string; email: string }
) => Promise<Response>;

export function withAuth(handler: AuthenticatedHandler) {
  return async (req: Request): Promise<Response> => {
    const authHeader = req.headers.get('authorization');
    if (!authHeader?.startsWith('Bearer ')) {
      return Response.json({ error: 'Missing Authorization header' }, { status: 401 });
    }
    
    const user = await verifyToken(authHeader.slice(7));
    if (!user) {
      return Response.json({ error: 'Invalid or expired token' }, { status: 401 });
    }
    
    return handler(req, user);
  };
}

// api/profile.ts - Using the middleware
import { withAuth } from '../lib/withAuth';

export default withAuth(async (req, user) => {
  const profile = await prisma.user.findUnique({
    where: { id: user.userId },
    select: { id: true, email: true, name: true }
  });
  return Response.json(profile);
});

Environment Variables

Auto-Injected Variables

These are available in every Lambda function automatically:
NODE_ENV=production
AWS_REGION=us-east-1
HIVEKU_PROJECT_ID=your-project-id
DATABASE_URL=... (if provisioned)

Build-Time vs Runtime

  • NEXT_PUBLIC_* variables are bundled at build time and exposed to the browser
  • All other variables are available only at runtime, server-side only
NEXT_PUBLIC_* variables require a redeploy to take effect after changes.

Troubleshooting

Check if you’re hitting the 30s default timeout. Increase in hiveku.json, or optimize your code. Database queries without connection pooling are a common cause.
Use dynamic imports, check for accidentally bundled devDependencies, exclude large assets. Consider splitting into multiple functions.
You’re using a package with native bindings (bcrypt, sharp, etc.). Switch to a pure JS alternative or request a Lambda Layer.
Hiveku adds CORS headers automatically. If you still see errors, check your frontend is calling the correct URL and your API returns proper response format.
Make sure the variable is set in Hiveku dashboard. NEXT_PUBLIC_* vars need a redeploy to take effect. Server-side vars are only available in Lambda, not the browser.
Reduce bundle size, increase memory (more memory = faster CPU), minimize top-level imports. Consider keeping functions warm with scheduled pings.
Lambda can scale to many instances. Use a connection pooler (Supabase, PlanetScale) or set your pool size to 1-2 connections per function.
Lambda /tmp is ephemeral. Files may persist between invocations on the same instance but will be cleared. Use S3 for persistent storage.