Resource
Resources are the core building blocks of Alchemy. Each resource represents a piece of infrastructure or configuration that can be created, updated, and deleted automatically.
What is a Resource?
Section titled “What is a Resource?”A Resource is simply a memoized async function that implemented a lifecycle handler for three phases:
create
- what to do when first creating the resourceupdate
- what to do when updating a resourcedelete
- what to when deleting a resource
Resource ID
Section titled “Resource ID”When creating a resource, you always pass an id
that is unique within the Resource’s Scope.
await MyResource("id")
This ID is what Alchemy uses to track the state of the resource and trigger the appropriate create/update/delete phase.
Input Props
Section titled “Input Props”Each Resource has an interface for its “input properties”
export interface DatabaseProps { name: string; branchId: string; projectId: string; // Other properties...}
Output Attributes
Section titled “Output Attributes”Each Resource has an interface for its “output attributes”:
export interface Database extends Resource<"neon::Database">, DatabaseProps { id: string; createdAt: number; // Additional properties...}
Physical Name
Section titled “Physical Name”Depending on the Resource provider, a Resource may have a “physical name” that is used to identify the resource in the infrastructure provider.
For example, a Cloudflare Worker name must be unique within a Cloudflare account.
const worker = await Worker("worker1", { name: "worker1", // <- physical name});
If you do not provide a physical name, Alchemy will generate a unique name for you based on the Application name, Resource ID, and Stage.
const app = await alchemy("my-app");
const worker = await Worker("worker1");
console.log(worker.name); // "my-app-worker1-${stage}"
If you run this with alchemy deploy --stage prod
, the worker name will be my-app-worker1-prod
.
Provider
Section titled “Provider”Each Resource exports a “Provider” function with a globally unique name and an implementation of the lifecycle handler logic.
export const Database = Resource( "neon::Database", async function(this: Context<Database>, id: string, props: DatabaseProps): Promise<Database> { if (this.phase === "delete") { // Delete resource logic // ... return this.destroy(); } else if (this.phase === "update") { // Update resource logic // ... return this({/* updated resource */}); } else { // Create resource logic // ... return this({/* new resource */}); } });
Let’s break this down a bit futher, since it may seem confusing at first.
Fully Qualified Name (FQN)
Section titled “Fully Qualified Name (FQN)”Each Resource has a globally unique name (aka. fully qualified name), e.g "neon:Database"
:
export const Database = Resource("neon::Database"),
Alchemy and uses this FQN to delete orphaned resources (stored in your State files) by looking up the corresponding “provider”.
Lifecycle Function
Section titled “Lifecycle Function”The Resource’s lifecycle handler is defined using an async function
declaration with 3 required arguments:
async function( // the resource's state/context is bound to `this` this: Context<Database>, // the id of the resource (unique within a SCope) id: string, // the input properties props: DatabaseProps): Promise<Database>
Lifecycle Phases
Section titled “Lifecycle Phases”The lifecycle handler is a simple function that handles the 3 phases: "create"
, "update"
or "delete"
:
if (this.phase === "delete") { // Delete resource logic // ... return this.destroy();} else if (this.phase === "update") { // Update resource logic // ... return this({/* updated properties */});} else { // Create resource logic // ... return this({/* initial properties */});}
Create
Section titled “Create”To construct the resource (including your properites and Alchemy’s intrinsic properties), call this(props)
with your output properties:
return this({/* updated properties */});
What’s going on here? this
is a function? Huh?
Alchemy resources are implemented with pure functions, but are designed to emulate classes (except with an async constructor that implements a CRUD lifecycle handler).
this
is analagous to super
in a standard class:
return super({/* updated properties */});
Destroy
Section titled “Destroy”When a resource is being deleted, you must return this.destroy()
to signal that the resource deletion process is complete.
Destroy Strategy
Section titled “Destroy Strategy”By default, Alchemy will destroy resources in a sequential order. You can change this behavior for a Resource by passing the destroyStrategy
option to the Resource constructor.
const Database = Resource( "neon::Database", { destroyStrategy: "parallel" }, async function(this: Context<Database>, id: string, props: DatabaseProps): Promise<Database> { if (this.phase === "delete") { return this.destroy(); } // these sub-resources will be deleted in parallel during the Database resource deletion await SubResource("sub-resource", {}); await OtherResource("other-resource", {}); });
Adoption
Section titled “Adoption”When creating a resource, Alchemy will fail if a resource with the same name already exists. Resource adoption allows you to opt in to using the pre-existing resource instead.
You can opt-in to adoption on a per-resource basis:
// Without adoption - fails if bucket already existsconst bucket = await R2Bucket("my-bucket", { name: "existing-bucket",});
// With adoption - uses existing bucket if it existsconst bucket = await R2Bucket("my-bucket", { name: "existing-bucket", adopt: true,});
Or set --adopt
to adopt all resources without changing code:
alchemy deploy --adopt
During the create phase, if a resource already exists:
- Without adoption (default): Throws an “already exists” error
- With adoption: Finds and adopts the existing resource
Replacement
Section titled “Replacement”Sometimes it’s impossible to UPDATE a resource (e.g., you cannot rename an R2 Bucket). In these cases, you need to perform a REPLACE operation to:
- create a new version of the Resource and update references
- delete the old version of the Resource (or leave it orphaned)
Trigger Replacement
Section titled “Trigger Replacement”During the update phase, you can trigger a replacement by calling this.replace()
:
// Implementation patternif (this.phase === "update") { if (this.output.name !== props.name) { // trigger replace and terminate this "update" phase this.replace(); // (unreachable code) } else { return updateResource(); }}
Create new
Section titled “Create new”After you call this.replace()
, the “update” phase will terminate and be re-invoked with “create” (to create the new resource).
if (this.phase === "create") { return createNewResource();}
Delete old
Section titled “Delete old”After all downstream dependencies have been updated and you finally call app.finalize()
, Alchemy will then invoke the “delete” phase on the old resource.
const app = await alchemy("app");
// ... create resources
await app.finalize(); // finalize scopes by deleting "orphaned" and "replaced" resources
Immediate replacement
Section titled “Immediate replacement”It is sometimes required to destory the old resource before creating the one, e.g. when updating a resource that requires a unique name.
To address this you can trigger a replacement immediately by calling this.replace(true)
. This will destroy the old resource before creating the new one.
this.replace(true);
Testing
Section titled “Testing”See the Testing documentation for a comprehensive walkthrough on how to test your own resources.