Aurabase Logo
aurabasedocs
docs›Edge Functions›Test and deploy from CI

Test and deploy from CI

A workable loop from the first edit to an automatic deployment, with the limits of local emulation stated up front.

8 min read·Level intermediate·Revised on Oct 12, 2026
This English text was generated automatically from the French original and has not been reviewed yet.
#
Local

1. Run it locally

aura functions serve runs your file with Deno and restarts it when you save.

terminalbash
aura functions serve hello --port 54321
curl -X POST http://127.0.0.1:54321/ -H 'Content-Type: application/json' -d '{"name":"Ada"}'

It is a plain Deno server. Compared with the platform, it has:

  • No platform headers. x-aura-role and x-aura-user-id are absent: send them yourself with curl -H to test authorisation branches.
  • No secrets. Pass them in your shell: STRIPE_SECRET_KEY=sk_test_… aura functions serve checkout.
  • No limits. Memory, timeout, body size and rate limits are not applied.
  • Plain requests. You call the function directly, so path and method are those of your curl, not the SDK's invoke options.

The SDK cannot target this server, because invoke calls the platform route. Test the SDK path against a real project, as below.

#
Integration

2. Test it for real

Create a project for development (see your first function), deploy the function there, and test through the SDK. This covers the platform's identity headers, limits and error classes.

tests/hello.test.tstypescript
import { createClient, FunctionsHttpError } from '@aurabase/aurabase-js'
import { describe, expect, it } from 'vitest'

const aura = createClient(process.env.AURA_TEST_URL!, process.env.AURA_TEST_ANON_KEY!)

describe('hello', () => {
  it('greets', async () => {
    const { data, error } = await aura.functions.invoke<{ greeting: string }>('hello', { body: { name: 'Ada' } })
    expect(error).toBeNull()
    expect(data?.greeting).toBe('Hello, Ada!')
  })

  it('reports a missing function as a FunctionsHttpError 404', async () => {
    const { error } = await aura.functions.invoke('does-not-exist')
    expect(error).toBeInstanceOf(FunctionsHttpError)
    expect(error?.status).toBe(404)
  })

  it('refuses to list functions with a public key', async () => {
    const { data, error } = await aura.functions.list()
    expect(data).toBeNull()
    expect(error?.status).toBe(403)
  })
})

Tips:

  • Use a dedicated project, never production, and keep its keys in your CI secrets.
  • The invocation rate limit is 15 calls then 1 per second per IP. Space your tests, or run them serially.
#
CI

3. Deploy from CI

The aura CLI installs from npm on macOS Apple Silicon and from source elsewhere, so a Linux CI job is simpler with the HTTP API. Deploying is one POST per function, authenticated with a service_role key. This exact loop was run against a real project and the function answered afterwards.

.github/workflows/deploy-functions.ymlyaml
name: Deploy functions
on:
  push:
    branches: [main]
    paths: ['aurabase/functions/**']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy every function
        env:
          AURA_PROJECT_URL: ${{ secrets.AURA_PROJECT_URL }}
          AURA_SERVICE_ROLE_KEY: ${{ secrets.AURA_SERVICE_ROLE_KEY }}
        run: |
          set -euo pipefail
          for dir in aurabase/functions/*/; do
            name=$(basename "$dir")
            file="${dir}index.ts"
            [ -f "$file" ] || continue
            jq -n --arg name "$name" --rawfile code "$file" \
              '{name: $name, runtime: "deno", code: $code}' |
            curl -sS --fail-with-body -X POST "$AURA_PROJECT_URL/v1/functions" \
              -H "apikey: $AURA_SERVICE_ROLE_KEY" \
              -H "Content-Type: application/json" \
              --data-binary @-
            echo " deployed $name"
          done

Facts that make this safe to run on every push:

  • The POST replaces the code of an existing function and keeps its secrets, because the body has no env_vars.
  • A function is single-file: relative imports are not shipped. Bundle first if you use local modules.
  • There is no version history on the server. Your Git history is the rollback: to go back, revert the commit and let CI deploy again.

Then run your integration tests against the project you just deployed, in a following job.

#
Environments

4. Development, staging and production

One project is one environment. Create one project per environment, each with its own URL and keys, and let CI choose the target:

EnvironmentProjectDeployed when
Developmentone per developer or sharedon demand, from a branch
Stagingmy-app-stagingevery push to main
Productionmy-appa release tag, after the staging tests pass

Store AURA_PROJECT_URL and AURA_SERVICE_ROLE_KEY per GitHub environment, and set each function's secrets separately in each project. Nothing is shared between projects.

Last updated · Oct 12, 2026