Introducing Notation

This week I released a preview of a project that I’ve been working on.
Notation is a backend framework that compiles TypeScript to fully functional cloud applications. It frees developers from worrying about infrastructure, and lets them focus on building useful software.
Motivation
The idea emerged a couple of years ago. I was building an analytics platform and chose a monolithic tech stack, based around Postgres. This enabled rapid development, but as the product started growing, so did the list of scaling issues.
I’ve always been hesitant to start projects with a serverless/microservices architecture, and refactoring that analytics project reminded me why – developing cloud-native software has major overheads.
Platforms like AWS and GCP shift the focus away from building features, and onto managing infrastructure. Mastery of these (vendor-specific) skills often requires full-time specialism. Furthermore, getting distributed cloud services to form a coherent software system is inherently complex.

So, a proposition was conceived: make developing cloud-native software as productive (and fun) as developing a monolith. If successful, Notation will be the right choice at both the start of a project and as it scales up.
Hello World
Here’s the code for a serverless function that is triggered by an API endpoint:
import { lambda, apiGateway } from "@notation/aws";
export const DoSomething = lambda(async () => {
// do something
});
const api = apiGateway();
export const PostMessage = api.route({
method: "POST",
route: "/message",
handler: DoSomething,
});
Behind the scenes, Notation infers – based on the relationship between the two resources – that it needs to set up some other resources: a proxy integration, a lambda permission, an IAM role, and a policy attachment.
That’s a lot of configuration – even for this simple example – that the developer didn’t need to manage with infrastructure-as-code.
Type Safety
Application and infrastructure concerns are declared in the same type space. This allows the TypeScript compiler to ensure cloud services are compatible. (For example, the compiler would not allow you to put an Azure Function behind an AWS API Gateway).
Data passed between services can also be typed using a library like Zod.
Distributed Workflows
Serverless functions can be composed in ways that would normally require a library like Kafka or Airflow, or a lot of duct tape.
The programming model for creating workflows is intuitive and programmatic:
import { workflow } from "@notation/azure";
import { GetUsers } from "../functions/get-users.ts";
import { SendSms } from "../functions/send-sms.ts";
export default workflow(() => {
const users = GetUsers();
users.batch({ size: 100 }, (user) => {
SendSms(user);
});
});
The workflow code is evaluated at build time and generates a static definition of the workflow. In production, this is consumed by a workflow engine to determine how to execute and pass data between serverless functions.
Workflows are a powerful feature that open up a wide variety of use-cases:
- pipelines for processing events and data
- integrations for streaming chatbot responses across services
- automations that enable GDPR compliance
- media transcoders for optimising images and video
Release
Notation will be released as an open source project. There are lots of interesting challenges ahead, and I’m looking forward to working through them with the developer community.
If this sounds interesting to you, sign up for early access. And, if you have any questions or feedback, drop me a message!