| 1 | --- |
| 2 | title: Build an upload journal |
| 3 | description: Sign in, upload private files, process real storage events, and view Postgres-backed status with Neon and Alchemy. |
| 4 | --- |
| 5 | |
| 6 | Build a small upload application using Neon Postgres, private Object Storage, Functions, managed Better Auth, and a Vite frontend. A real storage event checks the uploaded metadata and records the result in Postgres. The browser never marks an upload ready itself. |
| 7 | |
| 8 | The complete source is in [`examples/neon`](https://github.com/alchemy-run/alchemy/tree/main/examples/neon). Use that directory as the runnable project; the following pages explain its changes one concept at a time. Both the default Effect Function and the native Fetch alternative implement the same browser API. |
| 9 | |
| 10 | ## Prerequisites |
| 11 | |
| 12 | Complete [Setup](/neon/setup) before deploying. Use Node.js 24, Bun, pnpm, and a Neon account with Object Storage and Functions available. Configure the Alchemy `testing` profile for Neon or supply `NEON_API_KEY` to the deployment process only. This tutorial selects `aws-us-east-2`, where the combined backend is available. Resources can incur normal Neon usage charges; no AI gateway, model call, credits, or plan purchase is required. |
| 13 | |
| 14 | Install the repository's workspace dependencies before running the example. Keep the local `.alchemy` state directory: it records which resources a later destroy command owns. |
| 15 | |
| 16 | ## Open the example |
| 17 | |
| 18 | ```diff lang="sh" |
| 19 | +cd examples/neon |
| 20 | ``` |
| 21 | |
| 22 | All subsequent commands run from this directory, so the migration and Function entrypoint paths resolve correctly. |
| 23 | |
| 24 | ## Deploy the Effect application |
| 25 | |
| 26 | ```diff lang="sh" |
| 27 | +pnpm deploy --profile testing --stage upload-journal |
| 28 | ``` |
| 29 | |
| 30 | The stack returns the website URL, API URL, Auth URL, project ID, branch ID, and bucket name. Open the website URL to create an account; it contains no database password or deployment API key. |
| 31 | |
| 32 | ## Use the native alternative instead |
| 33 | |
| 34 | ```diff lang="sh" |
| 35 | +pnpm deploy:native --profile testing --stage upload-journal |
| 36 | ``` |
| 37 | |
| 38 | This selects `alchemy.native.ts`, which deploys a separate stack with an ordinary asynchronous Fetch handler and an explicit `FunctionTrigger`. Choose one alternative for normal use; deploying both intentionally creates two independent backends. |
| 39 | |
| 40 | ## Run the frontend against a deployed backend |
| 41 | |
| 42 | ```diff lang="sh" |
| 43 | +VITE_API_URL='<apiUrl>' VITE_NEON_AUTH_URL='<authUrl>' pnpm dev:web |
| 44 | ``` |
| 45 | |
| 46 | Vite opens on `http://127.0.0.1:43187`. Managed Auth permits localhost for this tutorial, and the API defaults to wildcard CORS for bearer-token requests without cookies. Every data request still requires a verified JWT. For an origin-restricted deployment, set `UPLOAD_APP_ORIGIN` to the known frontend origin before deployment. Starting Vite does not create a fake backend or bypass authentication. |
| 47 | |
| 48 | :::caution[Deployment verification] |
| 49 | The example depends on the new Neon provider and Website integrations. The current SDK probes report stale runtime code and environment values after both configuration-only and full-ZIP redeploys, despite the requested active deployment ID advancing. There is no verified workaround. A successful control-plane update is therefore not proof that Functions serve the new values: verify runtime behavior after deployment. If `APP_ORIGIN`, Auth configuration, or Function deployment readiness fails, keep that failure visible rather than substituting deployment credentials or bypassing verification. |
| 50 | ::: |
| 51 | |
| 52 | ## Follow the implementation |
| 53 | |
| 54 | 1. [Create the database and storage](/neon/tutorial/backend/). |
| 55 | 2. [Protect and process uploads](/neon/tutorial/functions/). |
| 56 | 3. [Connect the browser](/neon/tutorial/frontend/). |
| 57 | 4. [Fork a preview and clean up](/neon/tutorial/previews/). |
| 58 | |
| 59 | ## Understand the tutorial's limits |
| 60 | |
| 61 | Processing verifies object size and content type; it does not scan for malware. The 10 MiB limit is an application check before signing and after delivery, not an S3-enforced upload quota. A signed URL is a temporary bearer capability and can be reused until expiry. Add rate limits, upload budgets, stronger content validation, and an appropriate download domain before exposing a production service. |
| 62 | |
| 63 | The example deliberately disables email verification and enables localhost. Follow [Production Auth](/neon/guides/production-auth) to prepare SMTP, OAuth callbacks, trusted origins, email verification, and localhost restrictions before launch. Signout revokes the managed session, but already issued JWTs can remain valid until their short expiry. |