Architect sessions

Developing database-backed, stateful web applications used to require a web server, a database server, a whole supporting cast of software and frameworks and all the near-constant maintenance those things required. Now anyone with a text editor can handle POST requests directly with a Lambda function and API Gateway. Read more about Functional Web Apps

The first primitive to understand for building stateful interactions on the web is session state. HTTP is a stateless protocol which is a fancy way of saying every HTTP request is like a completely clean slate. If we want to remember things between HTTP requests you need a session.

This guide will go over several ways to store session state within your app. There is an example app at the end that demonstrates how sessions work within Architect.

HTTP sessions

All @http defined routes are session capable via @architect/functions.

  • Requests are tagged to a session via a stateless, signed, encrypted, httpOnly cookie: _idx
  • Session data expires after a week of inactivity

This allows you to write fully stateful applications despite Lambda functions being completely stateless.

Manually read and write sessions:

// a simple request counter
let arc = require('@architect/functions')

exports.handler = async function http(req) {
  // reads the session from the request
  let session = await arc.http.session.read(req)
  // modify the state
  session.count = (session.count || 0) + 1
  // write the session to a cookie
  let cookie = await arc.http.session.write(session)

  return {
    statusCode: 200,
    headers: {
      'content-type': 'text/html; charset=utf8',
      'set-cookie': cookie
    },
    body: `<pre>${JSON.stringify(session, null, 2)}</pre>`
  }
}

Alternatively, use arc.http[.async]'s built-in session management for a simpler implementation:

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

async function handler(req) {
  let session = req.session
  session.count = (session.count || 0) + 1

  return {
    session,
    html: `<pre>${JSON.stringify(session, null, 2)}</pre>`
  }
}

exports.handler = arc.http.async(handler)

See the Node.js sessions reference for more details on arc.http and arc.http.session.

Strong secret key

Ensure your app has a strong secret key. It should have a minimum length of 16 bytes.

npx arc --env production --add ARC_APP_SECRET something-much-better-than-this

🎲 Quickly generate a secret with openssl:

npx arc env -e staging -a ARC_APP_SECRET $(openssl rand -hex 32)
npx arc env -e production -a ARC_APP_SECRET $(openssl rand -hex 32)

Example

1. Create an Arc project

mkdir -p ./mysesh
cd mysesh
npm init -f
npm install @architect/architect

2. Add app.arc

Create a project manifest app.arc file at the root of your project.

# ./app.arc
@app
bigsesh

@http
get /
post /count
post /reset

And generate the boilerplate code by running:

npx arc init

3. Install Node.js helpers

Add the @architect/functions runtime helper library to your project. This gives the request object a method to read and write sessions.

npm i @architect/functions

4. Read the session

Modify src/http/get-index/index.js to read the session if it exists and render the forms with the session state

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

async function home(req) {
  let count = req.session.count || 0

  return {
    // this is perfectly acceptable and FAST server side rendering
    html: `
<!doctype html>
<html>
  <body>
    <form method=post action=/count>
      <button>Count ${ count }</button>
    </form>
    <form method=post action=/reset>
      <button>Reset</button>
    </form>
  </body>
</html>
    `
  }
}

exports.handler = arc.http.async(home)

5. Mutate the session

Modify src/http/post-count/index.js to mutate the session and redirect home

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

async function counter(req) {
  let count = (req.session.count || 0) + 1
  return {
    session: { count },
    location: '/'
  }
}

exports.handler = arc.http.async(counter)

FYI: Per recommended security practice Architect applications use httpOnly cookies for storing session state; anyone can implement their own mechanism using Set-Cookie headers directly

6. Clear the session

Modify src/http/post-reset/index.js to clear the session state

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

async function reset(req) {
  return {
    session: {},
    location: '/'
  }
}

exports.handler = arc.http.async(reset)

For more information about arc.http.async helper, check out the documentation

7. Test locally

Preview by starting the dev server

npx arc sandbox

Navigate to localhost:3333 to test out the counter.