Skip to content

scalemule/nextjs

Repository files navigation

@scalemule/nextjs

ScaleMule SDK for Next.js applications.

Server-side authentication with HTTP-only cookies, CSRF protection, webhook handling, secrets management, and client-side hooks.

Install

npm install @scalemule/nextjs

Auth in 6 Lines

// app/api/auth/[...scalemule]/route.ts
import { createAuthRoutes } from '@scalemule/nextjs/server'

export const { GET, POST, DELETE, PATCH } = createAuthRoutes()

This creates all auth endpoints automatically:

  • POST /api/auth/register — register + set HTTP-only cookie
  • POST /api/auth/login — login + set HTTP-only cookie
  • POST /api/auth/logout — logout + clear cookie
  • GET /api/auth/me — get current user
  • POST /api/auth/forgot-password — request password reset
  • POST /api/auth/reset-password — reset password
  • POST /api/auth/verify-email — verify email
  • POST /api/auth/resend-verification — resend verification (session or email-only)
  • POST /api/auth/refresh — refresh session
  • PATCH /api/auth/me — update profile
  • DELETE /api/auth/me — delete account

API keys never reach the browser. Session tokens are HTTP-only cookies.

Environment Variables

# .env.local
SCALEMULE_API_KEY=sk_prod_xxx    # server-only, no NEXT_PUBLIC_ prefix
SCALEMULE_ENV=prod               # 'dev' | 'prod'
SCALEMULE_COOKIE_DOMAIN=.yourdomain.com  # optional, for subdomain sharing

Auth Route Options

export const { GET, POST, DELETE, PATCH } = createAuthRoutes({
  // CSRF validation (recommended for production)
  csrf: true,

  // Cookie configuration
  cookies: {
    domain: '.yourdomain.com',    // share across subdomains
    maxAge: 30 * 24 * 60 * 60,    // 30 days
  },

  // Lifecycle hooks
  onRegister: async (user) => { /* post-registration logic */ },
  onLogin: async (user) => { /* post-login logic */ },
  onLogout: async () => { /* cleanup */ },
})

Webhooks in 10 Lines

// app/api/webhooks/scalemule/route.ts
import { createWebhookHandler } from '@scalemule/nextjs/server/webhooks'

export const POST = createWebhookHandler({
  secret: process.env.SCALEMULE_WEBHOOK_SECRET,
  onEvent: {
    'storage.file.uploaded': async (event) => {
      console.log('File uploaded:', event.data.file_id)
    },
    'video.transcoding.completed': async (event) => {
      console.log('Video ready:', event.data.video_id)
    },
  },
})

HMAC-SHA256 signature verification and 5-minute replay protection included.

1-Line Auth Export

For zero-config auth (reads from env vars):

// app/api/auth/[...scalemule]/route.ts
export { GET, POST, DELETE, PATCH } from '@scalemule/nextjs/server/auth'

Client-Side Provider

// app/layout.tsx
import { ScaleMuleProvider } from '@scalemule/nextjs'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ScaleMuleProvider
          apiKey={process.env.NEXT_PUBLIC_SCALEMULE_API_KEY!}
          environment={process.env.NEXT_PUBLIC_SCALEMULE_ENV as 'dev' | 'prod'}
        >
          {children}
        </ScaleMuleProvider>
      </body>
    </html>
  )
}

Client-Side Hooks

useAuth()

'use client'
import { useAuth } from '@scalemule/nextjs'

function LoginPage() {
  const { login, register, logout, user, loading, isAuthenticated, error } = useAuth()

  const handleLogin = async () => {
    await login({ email, password })
  }
}

useContent()

import { useContent } from '@scalemule/nextjs'

function Gallery() {
  const { files, upload, loading, uploadProgress, refresh, remove } = useContent({ autoFetch: true })

  const handleUpload = async (file: File) => {
    await upload(file, {
      onProgress: (pct) => console.log(`${pct}%`),
    })
  }
}

useUser()

import { useUser } from '@scalemule/nextjs'

function Settings() {
  const { profile, update, changePassword, changeEmail, deleteAccount, exportData } = useUser()
}

useRealtime()

import { useRealtime } from '@scalemule/nextjs'

function LiveUpdates() {
  const { status, subscribe } = useRealtime({ autoConnect: true })

  useEffect(() => {
    const unsub = subscribe('notifications', (data) => {
      console.log('New notification:', data)
    })
    return () => unsub()
  }, [subscribe])
}

Server Client

For server-side operations beyond auth (API routes, server components, server actions):

import { createServerClient } from '@scalemule/nextjs/server'

const sm = createServerClient()

// Storage
const { data } = await sm.storage.getViewUrl(fileId)

// Auth (with session token)
const { data: user } = await sm.auth.me(sessionToken)

// Secrets
const secret = await sm.secrets.get('STRIPE_KEY')

// Vault bundles
const db = await sm.bundles.get('database/primary')

CSRF Protection

Middleware Setup

// middleware.ts
import { generateCSRFToken, CSRF_COOKIE_NAME } from '@scalemule/nextjs/server'
import { NextResponse } from 'next/server'

export function middleware(request) {
  const response = NextResponse.next()

  if (!request.cookies.get(CSRF_COOKIE_NAME)) {
    const token = generateCSRFToken()
    response.cookies.set(CSRF_COOKIE_NAME, token, {
      httpOnly: false,  // must be readable by JS
      sameSite: 'strict',
      secure: process.env.NODE_ENV === 'production',
      path: '/',
    })
  }

  return response
}

Enable in Auth Routes

export const { GET, POST, DELETE, PATCH } = createAuthRoutes({
  csrf: true,  // validates x-csrf-token header against cookie
})

Client-Side

// Read CSRF token from cookie and include in requests
const csrfToken = document.cookie.match(/csrf_token=([^;]+)/)?.[1]

fetch('/api/auth/login', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-csrf-token': csrfToken,
  },
  body: JSON.stringify({ email, password }),
})

Secrets & Vault

import {
  getAppSecret,
  requireAppSecret,
  getMySqlBundle,
  getRedisBundle,
  getS3Bundle,
  getOAuthBundle,
  getBundle,
} from '@scalemule/nextjs/server'

// Simple secrets
const apiKey = await getAppSecret('STRIPE_API_KEY')
const required = await requireAppSecret('WEBHOOK_SECRET')  // throws if missing

// Typed bundles
const db = await getMySqlBundle('database/primary')     // { host, port, user, password, database, connectionUrl }
const redis = await getRedisBundle('cache/main')        // { host, port, password, connectionUrl }
const s3 = await getS3Bundle('storage/uploads')         // { bucket, region, access_key_id, secret_access_key }
const oauth = await getOAuthBundle('google')            // { client_id, client_secret, redirect_uri }

// Generic bundle
const stripe = await getBundle<{ api_key: string; webhook_secret: string }>('external/stripe')

Secrets are cached for 5 minutes. Configure with configureSecrets({ cacheTtlMs, noCache }).

Testing

import { MockScaleMuleProvider, createMockUser, createMockFile } from '@scalemule/nextjs/testing'
import { render, screen } from '@testing-library/react'

test('shows user name', () => {
  render(
    <MockScaleMuleProvider user={createMockUser({ full_name: 'Jane' })}>
      <ProfilePage />
    </MockScaleMuleProvider>
  )
  expect(screen.getByText('Jane')).toBeInTheDocument()
})

License

MIT - ScaleMule Inc.

About

ScaleMule Next.js server/client integration SDK

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors