npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@prestomedia/fanstack

v1.0.2

Published

Connect Firebase Functions to Angular with end-to-end type safety and context-building middleware.

Readme

@prestomedia/fanstack

npm version npm downloads License: MIT Prestomedia, LLC

Connect Firebase Functions to Angular with end-to-end type safety and context-building middleware.

  • (F)irebase Functions
  • (A)ngular
  • (N)ode

Building Firebase Functions for an Angular app usually involves a lot of friction: writing the backend endpoint, manually wiring up httpsCallable on the frontend, copying and pasting TypeScript interfaces between projects, and praying you didn't make a typo.

Fanstack eliminates the boilerplate. It is a lightweight CLI and architecture that automatically watches your Firebase Functions and generates a strictly-typed Angular service.

If you change a backend payload requirement, your Angular build will fail immediately. No more guessing.

Why Fanstack?

  • 💯 100% Type Safety: Backend actions and frontend services share the exact same TypeScript interfaces.

  • Zero Express Overhead: Fanstack ditches bulky Express wrappers in favor of Firebase's native onCall architecture. You get automatic App Check enforcement, out-of-the-box CORS, and natively decoded auth tokens with zero cold-start penalty.

  • 🛠️ Elegant Middleware: A simple, sequential middleware pipeline for handling auth requirements, database connections, and custom claims before your action ever runs.

  • 🤖 Automated CLI: fan init scaffolds your backend architecture, and fan generate builds your Angular SDK on the fly.

  • 🚀 Angular Service: Simply inject the FuncService and make calls like await func.folder.action()

Quick Start

1. Installation

Run this inside your Firebase functions directory:

npm install -D @prestomedia/fanstack

2. Setup

npx fan init

This creates a fan folder in your backend with pre-configured middleware, an action context, and an example action

[!IMPORTANT] Move the contents of fan-index-example.ts into your index.ts file so the action endpoint is ready to be called from the generated Angular service.

3. Generate the Client

Create an angular service that calls your action functions.

npx fan generate --watch

🪄 How It Works

With Fanstack, you organize your backend into Actions. Every .action.ts file in your fan/actions/ folder automatically becomes an executable endpoint on the frontend.

The Backend Action

Simply define your payload, your response, and your logic. Fanstack automatically provides a secure ActionContext containing the user's natively verified Firebase Auth token.

File: fan/actions/users/update-profile.action.ts

import { ActionContext } from '../../action-context';
import { UpdateProfilePayload, UserProfile } from '../../interfaces/users';

export const action = async (
	payload: UpdateProfilePayload,
	ctx: ActionContext,
): Promise<UserProfile> => {
	// The `requireAuth` middleware already verified the auth token
	// The `readUserProfile` middleware already loaded the user from the db

	const userId = ctx.user.id;

	// Do your database logic here...
	return { success: true, newName: payload.name };
};

[!TIP] The first parameter (payload) is required. Use payload: void when an action does not have a payload in.

[!TIP] The payload parameter and return interfaces must be placed in the /fan/interfaces folder and not reference anything outside that folder. The generator scoops up all those interfaces and includes them with the generated Angular service.

[!TIP] You can use primitive types or inline JavaScript objects, too.

The Generated Angular Service

Run fan generate and Fanstack will build FuncService right into your Angular app. Your frontend code is incredibly clean and completely typesafe. Your actions folder structure is transformed into namespaces within the service.

File: src/app/my.component.ts

import { Component, inject } from '@angular/core';
import { FuncService } from '../core/services/fan-func-service.gen';

@Component({ ... })
export class MyComponent {

  private func = inject(FuncService);

  async saveProfile() {
    try {
        // Fully autocompleted, perfectly typed, and nested by your folder structure!
        // The userId is determined by the auth in the middleware
        const result = await this.func.users.updateProfile({
        name: 'Jane Doe'
      });

      console.log('Saved!', result.success);
    } catch (error) {
      console.error('Update failed', error);
    }
  }
}

🧠 The Middleware Pipeline

Fanstack uses a blazing-fast, array-based middleware pipeline. You don't need heavy HTTP frameworks. Just write simple async functions that mutate your ActionContext or throw standard Firebase HttpsError exceptions.

File: fan/middleware/require-admin.ts

import { CallableRequest, HttpsError } from 'firebase-functions/v2/https';
import { ActionContext } from '../action-context';

export const requireAdmin = async (req: CallableRequest, ctx: ActionContext): Promise<void> => {
	if (!ctx.auth) {
		throw new HttpsError('unauthenticated', 'Please log in.');
	}

	// Assuming 'admin' was added elsewhere with Firebase's setCustomUserClaims()
	if (ctx.auth.token.admin !== true) {
		throw new HttpsError('permission-denied', 'Admin access required.');
	}
};

Drop it into your middleware array in fanstack.ts and it runs automatically before your actions!

[!NOTE] The middleware can add anything to the ActionContext. The ActionContext is then passed to the action function.

🗄️ Connect to MySql (Google Cloud Sql)

Add middleware that provides a MySql connection using @prestomedia/sql-runner

import { CallableRequest, HttpsError } from 'firebase-functions/v2/https';
import { ActionContext } from '../action-context';
import { SqlPool, SqlRunner } from '@prestomedia/sql-runner';

// Singleton for the cloud function instance.
const sql: SqlRunner | undefined = undefined;

/**
 * Middleware: Connect to Database
 * Instantiate a database connection and attach it to the context.
 */
export const connectToDatabase = async (
	req: CallableRequest,
	ctx: ActionContext,
): Promise<void> => {
	if (sql == undefined) {
		// The PoolOptions.connectionLimit should match the cloud function max instances
		sql = new SqlPool(ctx.env.sqlConfig);
	}
	ctx.sql = sql;
};

File: fan/actions/users/update-profile.action.ts

import { ActionContext } from '../../action-context';
import { UpdateProfilePayload, UserProfile } from '../../interfaces/users';

export const action = async (
	payload: UpdateProfilePayload,
	ctx: ActionContext,
): Promise<UserProfile> => {
	// The `requireAuth` middleware already verified the auth token
	// The `readUserProfile` middleware already loaded the user from the db

	const userId = ctx.user.id;
	const query = `update user set name = ? where userId = ?`;
	await ctx.sql.query(query, [payload.name, userId]);

	return { success: true, newName: payload.name };
};

License

Copyright © 2026 Prestomedia, LLC. Licensed under the MIT License.