Skip to main content

stack-1-angular-nodejs

Stack 1: Angular + TypeScript Backend (Express + Prisma)

Stack 1 is the "best balance of speed and TwinSpires comfort" option: it keeps Angular (which TwinSpires already knows), introduces a TypeScript backend on Node/Express, and standardizes on a single web language (TypeScript). While Stack 3 would be fastest for a React/Node delivery team, Stack 1 offers the best compromise between build speed and TwinSpires engineering team comfort.


1. Overview

  • Frontend: Angular (TypeScript)
  • Backend: NodeJS (TypeScript): Express or NestJS (see Node.js backend options comparison for details)
  • Database: PostgreSQL (or MySQL) via Prisma
  • Infra (example):
    • Frontend: S3 + CloudFront (static hosting)
    • Backend: Lambda + API Gateway (Serverless Framework) or containerized on ECS/Kubernetes
  • CI/CD: GitHub Actions (separate pipelines for frontend and backend)

This stack keeps the clear FE/BE separation TwinSpires is used to from Spring Boot + Angular, but with a lighter, more web-native toolchain.


2. Architecture

At a high level:

  • Angular SPA is built and deployed as static assets behind CloudFront.
  • Express API runs in a serverless function or container, exposing JSON endpoints.
  • Prisma manages database access and migrations with a type-safe schema.

3. Sample repo structure

Using the example repo in this project:

stack1-angular-express/
frontend/ # Angular app
src/app/
components/
services/
pages/
backend/ # NodeJS(express)
src/
routes/
services/
repositories/
db/
prisma/
schema.prisma
tech-stack-docs/ # Docusaurus documentation (this site)

Key points:

  • routes/: Express route handlers, thin controllers that map HTTP → service calls.
  • services/: Business logic, orchestrating repositories and external APIs.
  • repositories/: Data access layer using Prisma, isolated from services.
  • db/ / prisma/: Database client, schema, and migrations.

4. Example vertical slice

This slice shows a simple “Todos” list: Angular calls a REST endpoint implemented in Express, which reads from the DB via Prisma.

Backend – Express route

// backend/src/routes/todos.ts
import { Router } from "express";
import { listTodos } from "../services/todoService";

const router = Router();

router.get("/", async (_req, res) => {
const todos = await listTodos();
res.json(todos);
});

export default router;

Backend – Service + Prisma

// backend/src/services/todoService.ts
import { prisma } from "../db/client";

export async function listTodos() {
return prisma.todo.findMany();
}

Frontend – Angular service

// frontend/src/app/services/todo.service.ts
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs";

export interface Todo {
id: number;
title: string;
completed: boolean;
}

@Injectable({ providedIn: "root" })
export class TodoService {
private readonly baseUrl = "/api/todos";

constructor(private http: HttpClient) {}

getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.baseUrl);
}
}

Full example repo: https://github.com/MillionOnMars/tech-stack-comparison/tree/main/stack1-angular-express

This pattern scales naturally: each vertical slice adds:

  • One or more Angular components/routes
  • One Angular service method
  • One Express route (and perhaps service + repository methods)
  • Potential schema and migration changes in Prisma

5. CI/CD outline (example with GitHub Actions)

We can keep separate pipelines for frontend and backend while still treating each feature as a vertical slice.

Frontend (Angular)

  1. On pull request:
    • npm ci
    • npm run lint
    • npm test
    • npm run build
    • Deploy a preview environment (e.g., temporary S3/CloudFront distro).
  2. On merge to main:
    • Build production assets.
    • Upload to S3.
    • Invalidate CloudFront cache.

Backend (Express + Prisma)

  1. On pull request:
    • npm ci
    • npm run lint
    • npm test
    • Build and run integration tests (optionally with a disposable database).
  2. On merge to main:
    • Run Prisma migrations against a staging database.
    • Build and publish Docker image or Lambda package.
    • Deploy via Serverless Framework or IaC (CloudFormation/Terraform).

Vertical slice flow

  • A feature branch can update both frontend/ and backend/.
  • CI validates both sides together.
  • Once merged, both pipelines deploy, resulting in an end-to-end slice being live.

6. Pros

  • Single language across the web stack:
    • TypeScript in both Angular and Express.
    • Easier shared understanding of types, DTOs, and error handling.
  • Minimizes frontend change:
    • Keeps Angular, which the team already knows well.
    • Backend is new (Node/Express), but the language (TypeScript) is conceptually close to Java: classes, interfaces, generics.
  • Fast iteration:
    • Express and Prisma are lightweight and productive.
    • Thin vertical slices can be developed and deployed quickly.
  • Serverless- and container-friendly:
    • Same codebase can run on Lambda or in long-lived containers.

7. Cons & risks

  • New backend runtime for Twinspires Team(Node/Express)
  • Less “batteries-included” than Spring/Nest:
    • More decisions about conventions (e.g., error handling, validation) unless we add small libraries.
  • Medium ML/AI fit:
    • TypeScript is not the primary language for ML/AI; tight integration will rely on calling out to Python services or jobs.

8. Fit for BrisNet

Stack 1 is the best fit when:

  • Build speed for our team (React/Node specialists):
    • High: NodeJS / Express is directly in our wheelhouse. Angular won't be the most comfortable for our team, I think, but we can adapt if necessary.
  • Comfort for TwinSpires engineering (Angular/Java specialists):
    • Frontend: High, because Angular remains the primary UI framework.
    • Backend: Moderate, because the runtime (Node/Express) is new even if the language (TypeScript) feels familiar to Java developers.
  • Want fast delivery with minimal frontend change.
  • Want a single main language (TypeScript) for web development.

In other words: Stack 1 is a compromise solution if TwinSpires doesn't agree with Stack 3 (which is the most appropriate option for our team and long-term strategic vision). If the priority is speed + low risk for TwinSpires' learning curve, Stack 1 is the most straightforward path to a modern BrisNet. It keeps Angular (familiar frontend), but introduces Node.js/Express on the backend (new runtime, though TypeScript language is conceptually similar to Java). This minimizes frontend change while modernizing the backend.