As of 2023, Server-Side Rendering (SSR) has become a cornerstone of modern web development. In the React world, NextJs has gained such widespread adoption that the official React documentation now suggests it when bootstrapping fresh projects. Meanwhile, the Vue documentation recommends Nuxt as the go-to path for transitioning a Vue app "from the example to a production-ready SSR app."

But where does Angular stand? Angular is widely known as Google's popular single-page application (SPA) framework, with a reputation for powering complex enterprise systems. Yet, while Angular has supported SSR via Angular Universal for years, developer surveys consistently rank SSR improvements as the top priority for the framework. In short, SSR remains one of Angular's current pain points. The silver lining is that the Angular team is actively enhancing Universal and the core framework, which will soon unlock Angular's full potential with contemporary SSR approaches. For a deeper dive, I recommend this insightful post by Jessica Janiuk from the Angular team.

The more encouraging news is that Angular SSR is already more advanced than you might expect. @brandontroberts has developed Analog, a remarkable community-driven metaframework similar to NextJs or Nuxt, built atop Angular, Vite, and Nitro. A dedicated forthcoming article will explore getting started with Analog in greater detail. Additionally, I encourage you to browse the official docs at analogjs.org for more information.

In this piece, we'll elevate Analog further by combining it with other tools to craft a fully integrated, exceptionally powerful application stack that ensures type safety all the way from the database down to the DOM rendering template. Angular's TypeScript integration is second to none, as the framework has been written entirely in TypeScript since its debut over a decade ago. Extending that rigorous focus on type safety across the entire application is a natural progression. With both the client and server sides implemented in TypeScript, Analog positions us perfectly to take full advantage of this, especially as the TypeScript ecosystem continues to gain impressive server-side features.

When we enhance these two foundational components with other outstanding (and largely typesafe) technologies, the resulting stack is: Supabase, Prisma, Analog, tRPC, Tailwind, Angular, and Nx. For short: SPARTAN.

Naturally, all the source code is accessible to you. If you're already eager to dive in and start experimenting, feel free to explore the repository here.

I'll strive to offer persuasive rationale for each technology in the stack. Keep reading to discover why I believe they are worthy of powering your next full-stack project.

The SPARTAN Stack

Let’s get practical and examine the individual components that constitute the SPARTAN stack:

Nx

We begin with the workspace—the environment where your code lives and where features tailored to your users' needs are built. Think of it as a craftsman's workshop, initially bare with just a workbench. As you progress and tackle various projects, you gradually fill it with the necessary tools. Our seasoned uncle Nx has years of woodworking experience and shows up on day one with a fully stocked toolbox containing every conceivable instrument. But let's shift back to software:

Nx is a next-generation build system that delivers top-tier monorepo support and robust integrations with every major JavaScript framework. With it, you can use frontend frameworks such as Angular or backend frameworks like NestJs to design, test, and build projects of any scale.

The platform includes a powerful set of utilities that drastically simplify monorepo management. A monorepo is a version-controlled repository that houses multiple projects and libraries. By letting us share components and types not only across various frontend libraries but also between the frontend and backend, monorepos make it easy to boost the DRYness of our codebase. For us, sharing code between client and server in a single repository is a real breakthrough. Countless other monorepo advantages exist, and far more comprehensive articles have covered them.

To begin with Nx, run the command below, and then pick the options that follow:

npx create-nx-workspace@latest

✔ Choose your style                     · integrated
✔ What to create in the new workspace   · angular
✔ Repository name                       · spartan
✔ Application name                      · app
✔ Default stylesheet format             · css
✔ Enable distributed caching to make your CI faster · No
Enter fullscreen mode Exit fullscreen mode

Keep in mind that the commands shown here are just to demonstrate a baseline setup. A significant amount of configuration is needed to make the app fully operational. The quickest path to hitting the ground running is to clone the GitHub repository.

Angular

Executing that command generates a fresh Nx workspace, pre-configured for Angular development. Nice job! At this point, you've laid the groundwork for your SPARTAN project.
Before we move forward, here's a quick rundown on why Angular is my go-to choice:

Angular delivers opinionated routing, a robust HttpClient for handling async web requests, and—my absolute favorite—a stellar dependency injection system. It hands developers everything needed to craft exceptional apps straight away. This isn't just a library; it's an entire framework in itself.

Beyond that, Angular is built on TypeScript. This brings static type checking straight into the IDE, which catches bugs earlier in the process. This advantage really shines when both client and server are written in TypeScript. The result is end-to-end type safety that stretches from the database all the way up to the template rendering our data in the DOM.

When starting fresh, it's easy to forget that apps on the web get used by people with a wide range of abilities. For some, assistive technology may be essential to interact with our products. Accessibility (a11y) is woven directly into the Angular framework itself. The Angular team treats accessibility as a priority by default. The Angular Component Development Kit is a prime example. Its a11y features are outstanding. It provides all you need to build your own extensible, visually appealing, and accessible components, and it's maintained by the creators of the framework. Crafted with Angular, for everyone.

Building with Angular also means building with the community. That community is arguably Angular's greatest asset. It's committed group of people from a great variety of backgrounds. The spearhead here is the exceptional Angular core team. They care about users and developers alike, making sure every voice gets heard when new features are planned and constructed. Angular genuinely backs excellent community initiatives, and the community pays that forward by offering tremendous tools and features on top of an already great framework.

Analog

We've made a solid beginning, with a complete framework as the core of our stack. Now, let's bolt Analog onto Angular and give it some added muscle. Adding Analog to your workspace is a breeze with the newly released Nx plugin:

Get the @analogjs/platform installed:

npm i @analogjs/platform
Enter fullscreen mode Exit fullscreen mode

To get started, scaffold a new Analog project with the default setup already included:

nx g @analogjs/platform:app analog-trpc
Enter fullscreen mode Exit fullscreen mode

Analog is a metaframework built on top of Angular. You get everything Angular has to offer, plus an expanded feature set:

For serving, building, and testing, Analog relies on Vite and Vitest. Vite is a very fast next-generation JavaScript bundler, and it opens the door to a large ecosystem of powerful Vite plugins. Pairing a mature framework like Angular with the benefits of Vite is genuinely impressive—so much so that the Angular team appears to agree, as it’s working on transitioning its built-in dev server to Vite.

Analog brings another standout capability to the table: out-of-the-box support for both Server-Side Rendering (SSR) and Static Site Generation (SSG) in Angular projects. This means Angular can effortlessly drive marketing sites, blog pages, and much more! Since SSR is enabled by default, you don't need to tweak any settings to get started.
SSG support is also built in, making it a breeze to ship your site as plain static files suitable for hosting on GitHub Pages, S3, or any static file server! You have full freedom to pick a completely static approach or take advantage of server-side rendering—either way, you get all the SEO and performance perks Analog provides. And the momentum doesn't stop there. Angular 16 already introduced non-destructive hydration and meaningful improvements to web vitals. Looking ahead, version 17 will bring zoneless apps driven by signals, promising even more impressive performance gains. Choosing Analog positions you perfectly to capitalize on these advancements.

There's also the matter of API (server) routes and file-based routing, which Analog fully supports. In an Analog app, your folder structure and file names directly dictate the routing—no need to manually define route arrays anymore. But don't worry: you still have access to the complete Angular router, including its guards and resolvers, simply by adding route metadata inside your files.

TailwindCSS

A solid application deserves a polished UI. Achieving that is easier said than done, yet some tools make the journey smoother. Tailwind is one such essential tool in my toolkit.
Tailwind is described as "a utility-first CSS framework packed with classes like flex, pt-4, text-center and rotate-90."
These utility classes allow you to tweak layout, colors, spacing, typography, shadows, and countless other properties directly within your HTML, eliminating the need for extra CSS files. For me, a few compelling reasons solidify Tailwind's role in the SPARTAN stack:

  • Its utility classes come with thoughtful design principles baked in.
  • Using Tailwind to craft reusable UI components delivers both consistency and flexibility instantly.
  • Tailwind pairs exceptionally well with Angular, whose component-based structure encapsulates UI pieces and lets you compose them into the exact experience you're after.
  • I'd go so far as to say Angular makes using Tailwind particularly enjoyable: Angular's directives are perfect for masking the often-maligned verbose markup. I even wrote a dedicated article on this directive and tailwind collaboration. Additionally, if you're feeling adventurous, you might use a technique like this to pull out your tw classes and preserve cleaner Angular templates:

Warning: This looks to be pretty divisive, so proceed with caution if you decide to try it.

Because our setup relies on Vite and Nx, incorporating Tailwind is just as simple. All we need to do is pull in the required packages.

npm install -D tailwindcss postcss autoprefixer
Enter fullscreen mode Exit fullscreen mode

Next, place a Tailwind configuration file in the application's root directory:

const { createGlobPatternsForDependencies } = require("@nrwl/angular/tailwind");
const { join } = require("path");

/** @type {import("tailwindcss").Config} */
module.exports = {
  darkMode: 'class',
  content: ["./index.html",
    join(__dirname, "src/**/!(*.stories|*.spec).{ts,html}"),
    ...createGlobPatternsForDependencies(__dirname)
  ],
  theme: {
    extend: {}
  }
};

Enter fullscreen mode Exit fullscreen mode

Last but not least, we guide Vite to execute PostCSS and generate the Tailwind utility classes by introducing a postcss.config.js file at the root of our project:

const { join } = require('path');

module.exports = {
  plugins: {
    tailwindcss: {
      config: join(__dirname, 'tailwind.config.js')
    },
    autoprefixer: {}
  }
};
Enter fullscreen mode Exit fullscreen mode

tRPC

Our frontend is now fully prepared! Yet, Analog quietly transformed us into full stack developers. Any code placed in the server folder genuinely runs on the server 🤯 It’s just that simple! Let’s harness the full power of having our API built in the same language and within the same repository. With tRPC, we can make server-client interactions incredibly fluid and completely type-safe.

tRPC, or TypeScript Remote Procedure Call, is a streamlined library for invoking backend functions directly from the client side. It drastically simplifies backend-frontend communication by leveraging TypeScript’s type inference, which automatically surfaces potential errors on the client even before you save your changes to server files! Employing tRPC feels like using an SDK tailored to your API's server code, instilling confidence in every endpoint you expose.

On the server side, you construct a tRPC API by defining procedures. These are composable functions that form the foundation of your backend. They combine easily and can be queries, mutations, or subscriptions. Routers group multiple procedures together. A common practice is to use the well-known Zod validation library to guarantee that client input matches exactly the expected shape of your procedure. Always remember to export the type of your API's tRPC router so it can be referenced in your frontend code.

Within the SPARTAN repository, I’ve already provided tRPC adapters for both the client within our Angular app and the Nitro server that Analog includes by default. This integration makes tRPC and Analog a breeze to use! If you’re curious about the implementation details, feel free to explore the @spartan/trpc package found in the libs/trpc directory. For deeper dives into tRPC, I highly recommend checking out their excellent documentation at trpc.io

Prisma

At this stage, our frontend, server, and the communication between them are all type-safe and smooth. We’re only a short way from achieving complete type safety extending from our database all the way to the template that renders the DOM. Prisma is the key to getting us there.

Prisma serves as a Node.js and TypeScript ORM, which stands for Object Relational Mapper. This type of tooling wraps the code needed for data manipulation, letting you skip raw SQL and interact with objects directly in your preferred language. For context, Hibernate is a popular ORM for Java and the Spring framework, .NET offers the Entity Framework, and TypeScript has Prisma. Yet Prisma is more than just a basic ORM—it provides an intuitive data model, automated migrations (a feature I can't stress enough how valuable it is), type safety, and auto-completion!

With Prisma, you embed the definition of your database schema directly into your codebase.
Leveraging this schema, Prisma generates a properly typed Client. This client acts as a query builder uniquely tailored to your schema, aware of every table, field, and relationship within your database! As a result, with Prisma, we achieve literal type safety from the database right down to the template.

Furthermore, Prisma handles SQL migrations automatically from the schema, tracks migration execution, and provides utilities to detect and fix conflicts or drifts between migrations and the current database schema. This is a complete game-changer, allowing your database and its associated types to grow and adapt in sync with your application code.

We can integrate Prisma into our setup like this:

npm install prisma --save-dev
Enter fullscreen mode Exit fullscreen mode

Then, we initialize the client with:

npx prisma init
Enter fullscreen mode Exit fullscreen mode

As a result, a prisma directory will appear at the root of our Nx workspace. Within it, you'll find a schema.prisma file. The content I placed in that file is shown below:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model note {
  id         BigInt    @id(map: "notes_pkey") @default(autoincrement())
  created_at DateTime? @default(now()) @db.Timestamptz(6)
  note       String?
}
Enter fullscreen mode Exit fullscreen mode

With this configuration, Prisma will generate a JavaScript/TypeScript client and establish a connection to a Postgres SQL database via the DATABASE_URL connection string, using a schema that includes a simple note table for our demonstration. Naturally, you will modify the schema according to your own project's requirements.

At this stage, your typesafe stack is fully set up for development, with the sole exception being the need for persistent data storage—your PostgresSQL database.

Supabase

PostgreSQL stands out as one of the most scalable databases available worldwide. It represents a sophisticated object-relational system that leverages SQL. With Postgres, you can securely manage large volumes of complex data at scale. It empowers developers to create highly advanced applications, execute administrative tasks, and build robust integrated environments. As an open source technology, it commands the trust of millions of developers globally.

Interestingly, every Supabase project is built on a dedicated PostgreSQL database. Supabase serves as an exceptional open source alternative to Firebase, underpinned by that same Postgres engine. Beyond its core capabilities, it offers additional features that become increasingly valuable as your project grows in complexity. These include authentication, instant APIs, edge functions, real-time subscriptions, storage, and more—creating a solid foundation for application development.

What's more, Supabase is fundamentally open source. You can review the source code. Options range from self-hosting to using their free tier for initial experimentation, or opting for one of their paid plans to access a fully managed, production-ready environment that scales according to your needs.

Supabase recently concluded their Launch Week 7, introducing a wealth of impressive features. Among them is an AI assistant integrated into their premium platform. This assistant understands your database schema and provides guidance when crafting complex SQL queries. Additionally, I'm particularly excited about their recent open source releases, including the PostgreSQL package manager database.dev. For PostgreSQL, dbdev serves a role similar to what npm does for JavaScript. It grants your database effortless access to packages that enhance its capabilities—such as full-text search or Snapchat-style one-time-read data access. Most notably, dbdev can load onto any PostgreSQL instance with the necessary core extensions, regardless of the Supabase platform. And of course, the code is open source. I'm eager to see how this project evolves, and I'm confident we'll soon leverage many more outstanding libraries. As Brandon Roberts puts it:

All that remains is to tie everything together and round out our type-safe, full-stack SPARTAN setup.

Our next move is to configure persistent storage and leverage Prisma to align our database schema with the Postgres instance our project depends on. When it comes to getting started with Supabase, you have two paths forward:

  1. Link up directly with your managed instance on supabase.com
  2. Run it locally through Docker

Option 1: Connecting to supabase.com instance

This route is a breeze! Once you sign up for an account, your initial project is automatically provisioned.
From there, you're all set to hook into your project's database right away.

Now, we'll establish the connection from our application to the Supabase Postgres database:

Place a .env file in the root of your Nx workspace, then insert the snippet below

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-SUPABASE-REFERENCE-ID].supabase.co:5432/postgres?schema=public"

Enter fullscreen mode Exit fullscreen mode

Setting this up is surprisingly straightforward. Prisma can handle the database management and ensure our schema is properly migrated to our Supabase instance.

Option 2: Connecting to local supabase instance

There is also a local version of Supabase's system you can run.

To get started, check out this guide! It does an excellent job of walking you through the setup process, and there are plenty of resources if you encounter any issues.

Looking for a quick setup? If you're on a Mac, here's the approach I used to get everything running:

Install supabase CLI
brew install supabase/tap/supabase
Enter fullscreen mode Exit fullscreen mode
Log into CLI
supabase login
Enter fullscreen mode Exit fullscreen mode

Head over to https://app.supabase.com/account/tokens, generate an access token, and drop it into your terminal.

Set up your Supabase project
# if you are in the spartan directory move UP!!!
cd ..
# create your project folder
mkdir spartan-supabase

# move into the new folder
cd spartan-supabase

# start a new git repository — important, don't skip this step
git init
Enter fullscreen mode Exit fullscreen mode
Start Supabase services
supabase init
Enter fullscreen mode Exit fullscreen mode

and

supabase start
Enter fullscreen mode Exit fullscreen mode
Important: Make sure Docker is running and configured correctly!

Docker was already up and running on my machine. Still, my current environment does not match the default configuration that Supabase anticipates.
To get things working temporarily, I executed this:

DOCKER_HOST=unix:///Users/[YOUR_USER_ACCOUNT_NAME]/.docker/run/docker.sock supabase start
Enter fullscreen mode Exit fullscreen mode

Visit the GitHub issue for further details.

Connect to local DB

Since all docker images must be fetched first, this step may take some time. After the process finishes, a console message resembling the one below will appear.

Started supabase local development setup.

         API URL: http://localhost:54321
          DB URL: postgresql://postgres:postgres@localhost:54322/postgres
      Studio URL: http://localhost:54323
    Inbucket URL: http://localhost:54324
        anon key: eyJh......
service_role key: eyJh......
Enter fullscreen mode Exit fullscreen mode

Set aside your cyber-security mindset for the moment—after all, you're running this locally—and copy the connection string:

postgresql://postgres:postgres@localhost:54322/postgres
Enter fullscreen mode Exit fullscreen mode

Create a .env file in the top-level directory of your Nx workspace, then include the connection string as shown:

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="postgresql://postgres:postgres@localhost:54322/postgres?schema=public"
Enter fullscreen mode Exit fullscreen mode

Great — you're now all set to connect to your local Supabase Postgres instance!

Initializing the DB

With the database up and running, the next step is to define its schema.
Prisma handles this with remarkable ease!

  • Simply push the schema from your schema.prisma file directly to the running DB
yarn prisma db push
Enter fullscreen mode Exit fullscreen mode
  • To wrap up, we generate the Prisma client with this command:
yarn prisma generate
Enter fullscreen mode Exit fullscreen mode

Your database is now fully configured and prepared.

Bringing it all together

Following these steps should get you very near to launching your own SPARTAN app. However, the most straightforward path to a complete full-stack setup is to clone or fork the provided repository—it already includes all the necessary fine-tuning for a smooth start. From there, you can dive straight into making modifications:

git clone https://github.com/goetzrobin/spartan.git
Enter fullscreen mode Exit fullscreen mode

The files you’ll probably want to examine first are:
index.page.ts located within the analog-trpc project:

import { Component, OnInit } from '@angular/core';
import { injectTRPCClient } from '../../trpc-client';
import { AsyncPipe, DatePipe, JsonPipe, NgFor, NgIf } from '@angular/common';
import { FormsModule, NgForm } from '@angular/forms';
import { note } from '@prisma/client';
import { waitFor } from '../../wait-for';

const inputTw = 'focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:outline-0 block w-full appearance-none rounded-lg px-3 py-2 transition-colors text-base leading-tight md:text-sm bg-black/[.05] dark:bg-zinc-50/10 focus:bg-white dark:focus:bg-dark placeholder:text-zinc-500 dark:placeholder:text-zinc-400 contrast-more:border contrast-more:border-current';
const btnTw = 'focus-visible:ring-2 focus-visible:ring-zinc-50 focus-visible:outline-0 flex items-center justify-center rounded-lg px-2 py-1.5 text-sm font-bold tracking-tight shadow-xl shadow-red-500/20 bg-[#DD0031] hover:bg-opacity-70 text-zinc-800 hover:text-primary-darker';

@Component({
  selector: 'analog-trpc-home',
  standalone: true,
  imports: [AsyncPipe, FormsModule, NgFor, DatePipe, NgIf, JsonPipe],
  host: {
    class: 'block h-full p-4'
  },
  template: `
    <div class='justify-center flex mt-20 mb-8 items-center'>
      <h1 class='font-medium italic text-6xl text-[#DD0031] font-bold'>SPARTAN</h1>
      <img class='ml-2 block w-32' alt='Spartan Logo' src='/assets/spartan.svg' />
    </div>
    <form class='py-2 flex items-center' #f='ngForm' (ngSubmit)='addPost(f)'>
      <input required autocomplete='off' class='${inputTw}' name='newTitle' [(ngModel)]='newTitle' />
      <button class='ml-2 ${btnTw}'>+
      </button>
    </form>
    <div class='mt-4'>
      <div class='mb-4 p-4 font-normal border border-zinc-500/40 rounded-md'
           *ngFor='let note of notes ?? []; trackBy: noteTrackBy'>
        <div class='flex items-center justify-between'>
          <p class='text-sm text-zinc-400'>{{note.created_at | date}}</p>
          <button class='!text-xs h-6 !bg-opacity-10 hover:!bg-opacity-50 !text-zinc-50 ${btnTw}'
                  (click)='removePost(note.id)'>x
          </button>
        </div>
        <p class='mb-4'>{{ note.note }}</p>
      </div>

      <div class='text-center rounded-xl p-20 bg-zinc-950/40' *ngIf='!loadingPosts && notes.length === 0'>
        <h3 class='text-xl font-medium'>No notes yet!</h3>
        <p class='text-zinc-400'>Add a new one and see them appear here...</p>
      </div>
    </div>
  `
})
export default class HomeComponent implements OnInit {
  private _trpc = injectTRPCClient();
  public loadingPosts = false;
  public notes: note[] = [];
  public newTitle = '';

  public ngOnInit() {
    waitFor(this._trpc.note.list.query().then(notes => this.notes = notes));
  }

  public noteTrackBy = (index: number, note: note) => {
    return note.id;
  };

  public addPost(form: NgForm) {
    if (!form.valid) {
      form.form.markAllAsTouched();
      return;
    }
    this._trpc.note.create.mutate({ title: this.newTitle }).then(() => this.fetchPosts());
    this.newTitle = '';
    form.form.reset();
  }

  public removePost(id: bigint) {
    this._trpc.note.remove.mutate({ id }).then(() => this.fetchPosts());
  }

  private fetchPosts() {
    this.loadingPosts = true;
    this._trpc.note.list.query().then(notes => {
      this.loadingPosts = false;
      this.notes = notes;
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

This example demonstrates how to use the tRPC client to fetch, modify, and remove data records from the database.

The client communicates with a tRPC service endpoint declared in the [trpc].ts file located under server/routes/trpc within the same analog-trpc project:

import { appRouter } from '../../trpc/routers';
import { createContext } from '../../trpc/context';
import { createTrpcNitroHandler } from '@spartan/trpc';
// export API handler
export default createTrpcNitroHandler({
  router: appRouter,
  createContext
});
Enter fullscreen mode Exit fullscreen mode

The appRouter is exported from server/trpc/routers/index.ts and aggregates every other router defined in your app. Here, that includes the notesRouter, located in the notes file within the same directory. Try modifying the source of that file—say, by deleting the list procedure—and watch how TypeScript instantly flags the resulting error in index.page.ts.

import { z } from 'zod';
import { router, publicProcedure } from '../trpc';

import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()

export const noteRouter = router({
  create: publicProcedure
    .input(
      z.object({
        title: z.string(),
      }),
    ).mutation(({ input }) => prisma.note.create({data: {
      note: input.title
      }})),
  list: publicProcedure.query( () =>   prisma.note.findMany()
  ),
  remove: publicProcedure
  .input(z.object({
    id: z.bigint()
  })).mutation(({input}) => prisma.note.delete({
      where: {
        id: input.id
      }
    }))
});
Enter fullscreen mode Exit fullscreen mode

Here it is! Full-stack type safety is now entirely within your reach. This is SPARTAN!

Spartan yelling this is SPARTAN

What's next

A top community request for Analog is enabling NestJs as a supported backend option. Nest stands out as a robust framework for crafting efficient, scalable server-side solutions on Node.js. Its application architecture fosters the creation of systems that are highly testable, scalable, loosely coupled, and straightforward to maintain. Since the design draws heavy inspiration from Angular, it aligns naturally with both Analog and SPARTAN in the abstract.

GitHub logo Feature: Use NestJS integration as SSR/http server #317

platform

Information

NestJS is very popular Node.js framework for building APIs and is widely used in the Angular ecosystem. This feature would allow NestJS to be used as the server for the /api routes, and provided integration as the backend for SSR.

Links:

In development, NestJS would be configured under the /api path. During the build it would host the Analog application and provide additional configured API routes.

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • [ ] Yes
  • [ ] No

By default, tRPC and NestJs don't integrate smoothly, and the tRPC maintainers have no plans to bridge them through an official plugin in the near future.

That said, other libraries deliver a comparable type-safe experience tailored for REST APIs, which play directly to NestJs' strengths. Specifically, I'm referring to ts-rest, which comes with a first-party NestJs adapter. I'm looking forward to digging into these alternatives and determining how SPARTAN can serve as the ultimate launchpad for your enterprise-grade full stack Angular projects.

With the introduction of signals in Angular 16, I intend to weave them deeply into the SPARTAN stack. @timdeschryver has built an impressive forms solution called ng-signal-forms, which runs entirely on signals and already offers a stellar developer experience even for the intricate cases we face in production apps. My plan is to incorporate it into the current build to demonstrate how signals are set to reshape Angular development going forward.

Then there's the excellent Taxonomy repository, a valuable reference for the NextJs ecosystem that illustrates a realistic application built with version 13. Ideally, the SPARTAN repo would evolve into similarly polished, exemplary material for the Angular community—that's the bold ambition driving my work. If this piece sparks your interest in what's achievable, feel free to share your feedback or even jump in as a contributor!

A promising horizon

Thanks to Analog, Angular has at last got a meta-framework of its own, letting us harness every remarkable feature Angular offers—opening up the full power of the Vite ecosystem and empowering all of us to craft stronger, faster, and even more spectacular full stack Angular apps.

Combined with robust tools such as TailwindCSS, tRPC, databases like Postgres, and open-source-driven infrastructure providers like Supabase, I can't help but be thrilled at the prospect of more individuals and organizations going all in on Analog (and maybe even the SPARTAN stack) down the road.

As ever, do you have burning questions or blog post suggestions? What are your thoughts on this initiative? Could you see yourself adopting it? I'd love to hear your perspective—please drop a comment or fire me a message.

In closing, if this article resonated with you, feel free to hit like and pass it along. For more of my content, follow me on Twitter or Github.