DEV Community

loading...
Cover image for Serverless dependency management in OpenJS Architect

Serverless dependency management in OpenJS Architect

Paul Chin Jr.
Curious Human. #Serverless #JavaScript Dev @ http://arc.codes; Dev Rel at http://begin.com; Prophet of Nicolas Cage; #PraiseCage
・5 min read

OpenJS Architect is an open-source framework for writing and deploying serverless applications to AWS. Each Architect project is comprised of many small and independent Lambda functions that are executed statelessly in their own isolated runtimes.

So how do we manage shared code and dependencies between functions?

Dependencies defined in your root package.json

You are likely to use some dependencies in each Lambda function that you are deploying like including the @architect/functions library. Architect has the ability to discover use your project's root package.json to install dependencies into your Lambda functions. Let's take a look at an example.

First, start by creating a new Architect project

npm init @architect ./arc-example
cd arc-example
npm install @architect/functions
Enter fullscreen mode Exit fullscreen mode

By installing @architect/functions to the root package.json it will be available to all your functions when you require it. We won't need to manage dependencies with a per-function package.json.

So now we can use it in our get-index function.

// src/http/get-index/index.js

let arc = require('@architect/functions')

async function route(req) {
  return {
    statusCode: 200,
    html: `<h1> Praise Cage </h1>`
  }
}

exports.handler = arc.http.async(route)
Enter fullscreen mode Exit fullscreen mode

src/shared and src/views

Another way we can handle shared code is with a special folder src/shared. Architect provides an easy way to abstract and reuse code in your functions. Most applications need to share logic, templates, or utilities. In order to do this, Architect uses a folder convention to copy the contents of src/shared and src/views into each function's node_modules directory.

It's important to note that the entire contents of src/shared are copied recursively. I suggest you keep the directory structure as flat as possible, and the payloads as small as possible to improve performance.

Let's create an example helper function.

src/shared example

To get started, create a new project from the command line.

npm init @architect ./arc-shared-views
Enter fullscreen mode Exit fullscreen mode

Next, we can modify the app.arc file in the root of the project with the following:

# app.arc file
@app 
arc-shared

@http
get /
get /answer
Enter fullscreen mode Exit fullscreen mode

Now we can start to build out our src/shared modules by creating a new folder at src/shared/helper.js. In this example, we need to make sure a number is converted to a string and this helper function will do the trick!

// src/shared/helper.js

function theAnswer() {
  //really important number that needs to be converted to a string
  let universe = 42 
  return universe.toString()
}

module.exports = theAnswer
Enter fullscreen mode Exit fullscreen mode

We can use this helper in all of our functions by just requiring it from @architect/shared. Modify the get-answer function with the following:

// src/http/get-answer/index.js

let Answer = require('@architect/shared/helper')

exports.handler = async function http (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> The Answer is </title>
      </head>
      <body>
        <p> This is the Answer: ${Answer()} </p>
      </body>
      </html>
    `
  }
}
Enter fullscreen mode Exit fullscreen mode

Run npm start from the command line and take a look at our code structure. Sandbox will hydrate our functions with a node_modules/@architect/shared directory which is part of the function's payload when deployed and executed.

.
├── src
│   ├── http
│   │   ├── get-index/
│   │   └── get-answer/
│   │
│   └── shared/
│       └── helper.js
│   
├── app.arc
└── package.json
Enter fullscreen mode Exit fullscreen mode

When you navigate to http://localhost:3333/answer you will be greeted with data from our shared module, and it can be used by any other function.

src/views example

The src/views folder is a special location that allows you to include code for each of your HTPP functions with a GET route. Continuing with our previous src/shared example we will include a layout template that your HTTP functions can use.

Modify the app.arc file to match the following:

@app
arc-shared

@http
get /
get /answer 
get /about
get /css/:stylesheet

@views
get / 
get /about
Enter fullscreen mode Exit fullscreen mode

What we've done is added two new routes - /about and css/:stylesheet, then declared that two of the routes / and /about should receive a copy of the modules in src/views.

Create a new folder and file, src/views/layout.js. In this file we'll write the following contents:

module.exports = function Layout (props) {
  props = props || {}
  let heading = props.heading || 'Architect views!'
  return `
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Architect example</title>
 <link rel="stylesheet" href="/css/styles.css">
</head>
<body>
 <h1>${heading}</h1>
</body>
</html>
`
}
Enter fullscreen mode Exit fullscreen mode

This is our shared view template that will be used by each GET route listed under the @views pragma in the app.arc file.

Next, we'll modify src/http/get-index/index.js with the following:

let Layout = require('@architect/views/layout')

exports.handler = async function http (request) {
  try {
    return {
      statusCode: 200,
      headers: {
        'content-type':'text/html; charset=utf8'
      }, 
      body: Layout()
    }
  } catch (e) {
    console.error(e)
    return {
      headers: {
        type: 'application/json; charset=utf8',
      },
      status: 500,
      body: JSON.stringify({
        name: e.name,
        message: e.message,
        stack: e.stack
      }, null, 2)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This function will call the layout file and return its output as the body of the response. Next, we can set up the about page to send a different set of props to the layout. Modify sec/http/get-about/index.js with the following:

let Layout = require('@architect/views/layout')

exports.handler = async function http (request) {
  try {
    return {
      statusCode: 200,
      headers: {
        'content-type':'text/html; charset=utf8'
      }, 
      body: Layout({heading: 'About'})
    }
  } catch (e) {
    console.error(e)
    return {
      status: 500,
      type: 'application/json; charset=utf8',
      body: JSON.stringify({
        name: e.name,
        message: e.message,
        stack: e.stack
      }, null, 2)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Now when /about is requested, this function will execute and be able to return the data being passed into Layout().

Finally, we have some finder control over which GET functions will have /src/views copied into it. We do this with the @views pragma in the app.arc file. We want to create a URL to our style sheet, but this function doesn't need access to the layout code. Only the GET routes under @views will have the src/views code copied into it. Our first route of /answer won't have src/views modules copied into node_modules.

Modify the code in src/http/get-css-000stylesheet/index.js with the following:

const styles = `
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
`
exports.handler = async function http (request) {
  return {
    statusCode: 200,
    type: 'text/css; charset=utf8',
    body: styles
  }
}
Enter fullscreen mode Exit fullscreen mode

OK! Go ahead and run npm start from the project root and navigate to http://localhost:3333 to see our app in action. Change the route to http://localhost:3333/about and you'll see that our proper were passed as expected.

See full example code in the repo: https://github.com/pchinjr/arc-shared-views-example

Discussion (0)