Case studies/Enterprise, SaaS & industrial
Consulting portal · innovation performance management

InnoMetrix

2018 - 2020

Cloud platform that puts financial and operational numbers behind innovation goals, strategies and initiatives.

Overview

InnoMetrix is an innovation performance management platform for enterprise transformation teams. It puts financial and operational numbers behind goals, strategies and initiatives: organizations plan and align initiatives through a stage-gate approval process, track IRR, payback, NPV and KPIs of their choosing, and analyze portfolio performance in dashboards and reports, with planning, execution and reporting in one cloud product.

The engagement was the consulting portal behind that product and the AWS platform under it - the place where a consulting team works with its client organizations: clients, teams, documents, settings, notifications, and periodic snapshots of how each portfolio is performing.

The backend is a serverless TypeScript monorepo: one Lambda per controller folder behind an API Gateway REST API, written on an in-house framework the studio carries between projects. That framework is decorator-driven - @Controller on the class, @Get, @Post, @Put and @Patch on the methods, @MongodbService for data access, a LambdaApplication that wires it together - so a controller reads like a small service rather than like a pile of handler files, and the split into functions is a directory convention instead of a deployment decision taken by hand.

Data lives in Amazon DocumentDB reached through the MongoDB driver, with the host and password pulled from SSM Parameter Store and Secrets Manager rather than from configuration, and the functions themselves running inside private VPC subnets. Schema changes go through migrate-mongo - around twenty migrations in the repository - and node_modules ship as a Lambda Layer so the deployment package stays small. Background work runs as its own functions: an events handler and a snapshots handler, both on EventBridge schedules.

Identity is Amazon Cognito, but authorization is the platform's own: a custom Lambda authorizer validates the JWT, then a policies, roles, groups and users model in the framework's access-manager package decides what the caller may touch. An export API is mounted deliberately outside the authorizer, so integrations can pull data without a portal session.

The portal itself is a React single-page app - React 17 on Create React App, routed with React Router and lazy-loaded pages behind a ProtectedRoute, styled with MUI and Emotion over a large SCSS layer. The working surfaces are the ones a consulting engagement actually needs: DevExtreme data grids for portfolio tables, Formik and Yup forms for the stage-gate steps, ApexCharts, Chart.js, gauges and sparklines for KPI dashboards, rich-text editing, drag-and-drop reordering, a calendar and maps. Axios interceptors attach the bearer token and log the user out on a 401, so an expired session ends cleanly instead of failing page by page. The build ships as static files to S3 behind CloudFront.

The public marketing site is a separate project and not part of that work: it ran on a HubSpot theme in 2024-2025 and on a Lovable and Supabase build from 2026.

What was built

Stack

Backend

TypeScript on AWS Lambda
Serverless monorepo, one function per controller directory
API Gateway (REST)
The portal's HTTP surface in front of the functions
In-house @datasub framework
Decorators for controllers, routes and MongoDB services, with an application class assembling the Lambda
Node.js
Lambda runtime, with a matching runtime for local offline runs

Data

Amazon DocumentDB
Primary store, reached through the MongoDB driver
migrate-mongo
About twenty migrations carrying the schema forward

Security & Access

Amazon Cognito
User pool for identity
Custom Lambda authorizer
JWT validation in front of the API
Policies, roles, groups and users
The platform's own authorization model in the framework's access-manager package
SSM Parameter Store + Secrets Manager
Database host and credentials, kept out of configuration files

Frontend

React 17 (Create React App)
Single-page portal, routed with React Router and lazy-loaded pages behind a ProtectedRoute
MUI + Emotion
Component library and styling over a large SCSS layer
DevExtreme
The portal's main data grid for portfolio tables
Formik + Yup
Forms and validation for stage-gate and settings screens
axios with interceptors
Bearer token on every request and automatic logout on a 401
ApexCharts, Chart.js, gauges, sparklines
KPI and portfolio dashboards
Rich text, drag-and-drop, calendar, Leaflet
Editing, reordering, scheduling and mapping surfaces

Infrastructure & DevOps

AWS CDK (TypeScript)
Infrastructure as code, functions in private VPC subnets, node_modules as a Lambda Layer
EventBridge
Schedules for the events and snapshots background jobs
SES, SQS, CloudFront
Mail, queues and content delivery around the platform
S3 + CloudFront
Static hosting for the portal build
Serverless Framework + serverless-offline
Local runs of the same functions, configured from TypeScript

Tooling & QA

Jest + ts-jest
Backend tests against an in-memory MongoDB
ESLint + typescript-eslint, Prettier
Linting across the backend monorepo and the portal
yarn workspaces, conventional commits
Monorepo package management with a commit convention enforced by tooling

Engineering highlights

The studio brought its own framework rather than the client's: decorators for controllers, routes and data access mean a new domain is a directory and a class, and the mapping from code to deployed Lambdas is a convention rather than a hand-maintained list.

Identity and authorization are deliberately separate - Cognito says who you are, the platform's own policies, roles and groups model says what you may touch - which is what a portal serving several client organizations needs.

The snapshots job is the product's memory: portfolio performance is recorded on a schedule, so the dashboards can show how initiatives moved rather than only where they stand today.

One export API sits outside the authorizer on purpose, so integrations pull data without pretending to be a logged-in user.