Greetings Curious Humans! We’re back with another installment of “Same Service, Different Tools”. Parts 1 to 3 covered SAM, CDK, and Terraform. Today, we’ll explore Architect, a serverless framework focusing on speed and developer agility. Architect is fully open-source and governed by the non-profit OpenJS Foundation. It sets itself apart by curating interfaces for a subset of AWS services that are critical to building web apps. In my opinion, it is the only serverless framework that offers a good local development workflow. We’ll continue using GitHub Codespaces as a base environment that you can fork from my repo, https://github.com/pchinjr/serverless-file-upload
You’ll need to create an admin
IAM user and set your /.aws/credentials
file:
[arc-admin]
aws_access_key_id=xxx
aws_secret_access_key=xxx
These are the credentials that the Architect CLI will use to deploy your project. One cool thing about Architect is that we’ll be able to build and test everything locally before we deploy!
To bootstrap a project we just need to open a terminal and run npm init @architect serverless-upload-arc
. This creates a folder called /serverless-upload-arc
that contains our initial project structure. Similar to main.tf
or template.yaml
, you will find an app.arc
file in the root. The .arc
file is our declarative manifest. You’ll notice right away that it’s quite small. Resources are declared with pragmas and minimal configuration.
@app
arc-example
@http
get /
@aws
# profile default
region us-west-2
Let’s break it down:
@app
is the project namespace@http
declares API Gateway routes and methods. Each route has a corresponding Lambda handler located in /src/http/
@aws
is the global AWS provider configurationNext, let’s look at the Lambda handler code in /src/http/get-index/index.mjs
:
// learn more about HTTP functions here: <https://arc.codes/http>
export async function handler (req) {
return {
statusCode: 200,
headers: {
'cache-control': 'no-cache, no-store, must-revalidate, max-age=0, s-maxage=0',
'content-type': 'text/html; charset=utf8'
},
body: `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Architect</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } .max-width-320 { max-width: 20rem; } .margin-left-8 { margin-left: 0.5rem; } .margin-bottom-16 { margin-bottom: 1rem; } .margin-bottom-8 { margin-bottom: 0.5rem; } .padding-32 { padding: 2rem; } .color-grey { color: #333; } .color-black-link:hover { color: black; }
</style>
</head>
<body class="padding-32">
<div class="max-width-320">
<img src="<https://assets.arc.codes/logo.svg>" />
<div class="margin-left-8">
<div class="margin-bottom-16">
<h1 class="margin-bottom-16">
Hello from an Architect Node.js function!
</h1>
<p class="margin-bottom-8">
Get started by editing this file at:
</p>
<code>
arc-example/src/http/get-index/index.mjs
</code>
</div>
<div>
<p class="margin-bottom-8">
View documentation at:
</p>
<code>
<a class="color-grey color-black-link" href="<https://arc.codes>"><https://arc.codes></a>
</code>
</div>
</div>
</div>
</body>
</html>
`
This might look strange at first, but it is a Lambda function triggered by API Gateway that returns an HTML string to render in the browser. Our example service doesn’t have a front-end client, but we can use this default route to try out local development. From the project root /serverless-upload-arc
open a terminal and run npx arc sandbox
, you should see Architect start a local server and Github Codespaces will forward the ports to a private URL. Ctrl+click
on https://localhost:3333
and you should see the index page. We can update the Lambda handler with custom text like “Praise Cage!” in the <h1>
element and see the changes in the returned HTML.
sandbox
is a local server that emulates AWS services like API Gateway and even Dynamodb. It does all this with Nodejs and doesn’t require any extra Java runtimes or Docker containers. We can incrementally build and improve our iteration cycles using the sandbox
environment without waiting for a full cloud deployment.
Let’s add a POST /upload route to app.arc
, make a new file /src/http/post-upload/index.mjs
for our Lambda handler, and use two plugins for creating a public S3 bucket and a local S3 server to emulate our FileUploadBucket
. Architect is extendable with plugins that hook into the sandbox
lifecycle and can modify the CloudFormation output with custom resources. Under the hood, Architect compiles the .arc
file into CloudFormation. Architect takes care of service discovery, IAM roles, code packaging, and service integrations. Check out the Architect playground to see a side-by-side comparison of how much CloudFormation you don’t have to write.
@aws
region us-east-1
profile arc-admin
@app
serverless-upload-arc
@http
get /
post /upload # new POST route
@plugins
architect/plugin-storage-public # enables public buckets
ticketplushq/arc-plugin-s3rver # runs a local s3 server
@storage-public
FileUploadBucket # creates a public bucket
import awsLite from '@aws-lite/client'
const s3Config = JSON.parse(process.env.ARC_S3RVER_CONFIG || '{}')
const aws = await awsLite({ ...s3Config, plugins: [ import('@aws-lite/s3') ] })
export async function handler(req) {
try {
const body = JSON.parse(req.body);
const decodedFile = Buffer.from(body.file, 'base64');
const params = {
"Body": decodedFile,
"Bucket": process.env.ARC_STORAGE_PUBLIC_FILEUPLOADBUCKET,
"Key": body.filename,
"ContentType": body.contentType
};
await aws.s3.PutObject(params).then(() => console.log('congrats!'))
return {
statusCode: 200,
body: JSON.stringify({ message: "Praise Cage!" }),
};
} catch (err) {
console.error(err);
return {
statusCode: 500,
body: JSON.stringify({ message: "Error uploading file", error: err.message }),
};
}
}