Skip to content

Components

Components are the building blocks of your app.

Every SST app is made up of components. Components in turn create one or more pieces of low level infrastructure. Components wrap these up into logical units that represent the high level concepts of your app.

For example, sst.aws.Function lets you add a Lambda function to your app, while sst.aws.Nextjs lets you deploy your Next.js app.

SST’s components use Pulumi behind the scenes to create the low level infrastructure. These are generated from Terraform modules. SST allows you to customize how these are created.

You can also use any Pulumi component in your SST app. This is useful if you want to create infrastructure that SST doesn’t support yet.


Providers

There are two types of components in your app.

  1. Native SST components, namespaced under sst.*
  2. And Pulumi components, namespaced under <provider>.*

A provider is the infrastructure service that the component is deployed to. SST’s components currently deploy to two providers.

  • AWS

    These components are namespaced under sst.aws.*. Internally they use Pulumi’s AWS Classic provider.

  • Cloudflare

    These components are namespaced under sst.cloudflare.*. Internally they use Pulumi’s Cloudflare provider.

Learn more about providers.


Constructor

To add a component to your app, you create an instance of it by passing in a couple of args. For example, here’s the signature of the Function component.

new sst.aws.Function(name: string, args: FunctionArgs, opts?: pulumi.ComponentResourceOptions)

Each component takes the following:

  • name: The name of the component. This needs to be unique across your entire app.
  • args: An object of properties that allows you to configure the component.
  • opts?: An optional object of properties that allows you to configure this component in Pulumi.

Here’s an example of creating a Function component:

const function = new sst.aws.Function("MyFunction", {
handler: "src/lambda.handler"
});

Name

There are two guidelines to follow when naming your components:

  1. Component names are global across your entire app.

  2. Use PascalCase for the component name. For example, MyFunction instead of myFunction or my_function.

These make it possible to link resources together in your SST app.


Args

Each component takes a set of args that allow you to configure it. These args are specific to each component. For example, the Function component takes FunctionArgs.

Most of these args are optional, meaning that most components need very little configuration to get started. Typically, the most common configuration options are lifted to the top-level. To further configure the component, you’ll need to use the transform prop.

Args usually take primitive types. However, they also take a special version of a primitive type. It’ll look something like Input<string>. We’ll look at this in detail below.


Transform

Most components take a transform prop as a part of their constructor or methods. It’s an object that takes callbacks that allow you to transform how that component’s infrastructure is created.

For example, here’s what the transform prop looks like for the Function component:

  • function: A callback to transform the underlying Lambda function
  • logGroup: A callback to transform the Lambda’s LogGroup resource
  • role: A callback to transform the role that the Lambda function assumes

The type for these callbacks is similar. Here’s what the role callback looks like:

RoleArgs | (args: RoleArgs => void)

So this takes either a RoleArgs object or a function that takes RoleArgs. Where RoleArgs is the props for the role that’s passed to Pulumi.

This allows you to either:

  • Pass in your own RoleArgs object.

    {
    transform: {
    role: {
    name: "MyRole"
    }
    }
    }

    This is merged with the original RoleArgs that were going to be passed to the component.

  • Or, pass in a callback that takes the current RoleArgs and mutates it.

    {
    transform: {
    role: (args) => {
    args.name = `${args.name}-MyRole`;
    }
    }
    }

$transform

Similar to the component transform, we have the global $transform. This allows you to transform how a component of a given type is created.

For example, to set a default runtime for all function compoennts.

$transform(sst.aws.Function, (args, opts) => {
args.runtime = "nodejs18.x";
})

Here, args and opts are what you’d pass to the Function component. Recall the signature of the Function component:

new sst.aws.Function(name: string, args: FunctionArgs, opts?: pulumi.ComponentResourceOptions)

Read more about the global $transform.


Properties

An instance of a component exposes a set of properties. For example, the Function component exposes the following propertiesarn, name, url, and nodes.

const functionArn = function.arn;

These can be used to output info about your app or can be used as args for other components.

These are typically primitive types. However, they can also be a special version of a primitive type. It’ll look something like Output<string>. We’ll look at this in detail below.


Some of these properties are also made available via resource linking. This allows you to access them in your functions and frontends in a typesafe way.

For example, a Function exposes its name through its links.


Nodes

The nodes property that a component exposes gives you access to the underlying infrastructure. This is an object that contains references to the underlying Pulumi components that are created.

For example, the Function component exposes the following nodesfunction, logGroup, and role.


Outputs

The properties of a component are typically of a special type that looks something like, Output<primitive>.

These are values that are not available yet and will be resolved as the deploy progresses. However, these outputs can be used as args in other components.

This makes it so that parts of your app are not blocked and all your resources are deployed as concurrently as possible.

For example, let’s create a function with a url.

const myFunction = new sst.aws.Function("MyFunction", {
url: true,
handler: "src/lambda.handler"
});

Here, myFunction.url is of type Output<string>. We want to use this function url as a route in our router.

new sst.aws.Router("MyRouter", {
routes: {
"/api": myFunction.url
}
});

The route arg takes Input<string>, which means it can take a string or an output. This creates a dependency internally. So the router will be deployed after the function has been. However, other components that are not dependent on this function can be deployed concurrently.

You can learn more about Input and Output types on the Pulumi docs.


Apply

Since outputs are values that are yet to be resolved, you cannot use them in regular operations. You’ll need to resolve them first.

For example, let’s take the function url from above. We cannot do the following.

const newUrl = myFunction.url + "/foo";

This is because the value of the output is not known at the time of this operation. We’ll need to resolve it.

The easiest way to work with an output is using .apply. It’ll allow you to apply an operation on the output and return a new output.

const newUrl = myFunction.url.apply((value) => value + "/foo");

In this case, newUrl is also an Output<string>.


Helpers

To make it a little easier to work with outputs, we have the following global helper functions.


$concat

This let’s you do.

const newUrl = $concat(myFunction.url, "/foo");

Instead of the apply.

const newUrl = myFunction.url.apply((value) => value + "/foo");

Learn more about $concat.


$interpolate

This let’s you do.

const newUrl = $interpolate`${myFunction.url}/foo`;

Instead of the apply.

const newUrl = myFunction.url.apply((value) => value + "/foo");

Learn more about $interpolate.


$jsonParse

This is for outputs that are JSON strings. So instead of doing this.

const policy = policyStr.apply((policy) =>
JSON.parse(policy)
);

You can.

const policy = $jsonParse(policyStr);

Learn more about $jsonParse.


$jsonStringify

Similarly, for outputs that are JSON objects. Instead of doing a stringify after an apply.

const policy = policyObj.apply((policy) =>
JSON.stringify(policy)
);

You can.

const policy = $jsonStringify(policyObj);

Learn more about $jsonStringify.


$resolve

And finally when you are working with a list of outputs and you want to resolve them all together.

$resolve([bucket.name, worker.url]).apply(([bucketName, workerUrl]) => {
console.log(`Bucket: ${bucketName}`);
console.log(`Worker: ${workerUrl}`);
})

Learn more about $resolve.