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

@noony-serverless/core

v0.8.0

Published

A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript

Downloads

522

Readme

Noony Serverless Framework

A powerful and flexible serverless middleware framework for Google Cloud Functions with full TypeScript support. This framework provides a clean, type-safe way to handle HTTP and Pub/Sub requests through a composable middleware system inspired by Middy.js.

Core Architecture

Handler System

The Handler class manages the middleware execution pipeline with before, after, and onError lifecycle hooks:

const handler = new Handler<RequestType, UserType>()
  .use(errorHandler())
  .use(bodyParser())
  .use(bodyValidator(schema))
  .handle(async (context) => {
    // Your business logic here
  });

Type-Safe Context

The context system provides full TypeScript support with generic typing:

interface Context<T = unknown, U = unknown> {
  req: CustomRequest<T>;     // Request with parsedBody and validatedBody
  res: CustomResponse;       // Response object
  container?: Container;     // TypeDI dependency injection
  error?: Error | null;      // Error handling
  businessData: Map<string, unknown>; // Inter-middleware data sharing
  user?: U;                  // Authenticated user data
}

Middleware Lifecycle

Middlewares support three lifecycle hooks:

  • before: Execute before the main handler
  • after: Execute after the main handler (reverse order)
  • onError: Handle errors (reverse order)

Quick Start

Installation

npm install @noony/serverless
# or
yarn add @noony/serverless

Basic HTTP Function

import { http } from '@google-cloud/functions-framework';
import { z } from 'zod';
import {
  Handler,
  ErrorHandlerMiddleware,
  BodyValidationMiddleware,
  ResponseWrapperMiddleware,
} from '@noony/serverless';

// Define request schema
const userSchema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
  age: z.number().min(18),
});

type UserRequest = z.infer<typeof userSchema>;

// Create handler with full type safety
const createUserHandler = new Handler<UserRequest, unknown>()
  .use(new ErrorHandlerMiddleware())
  .use(new BodyValidationMiddleware(userSchema))
  .use(new ResponseWrapperMiddleware())
  .handle(async (context) => {
    // TypeScript knows validatedBody is UserRequest
    const { name, email, age } = context.req.validatedBody!;
    
    // Your business logic
    const user = await createUser({ name, email, age });
    
    context.res.json({
      message: 'User created successfully',
      userId: user.id,
    });
  });

// Export Google Cloud Function
export const createUser = http('createUser', (req, res) => {
  return createUserHandler.execute(req, res);
});

Pub/Sub Function Example

import { cloudEvent } from '@google-cloud/functions-framework';
import { z } from 'zod';
import {
  Handler,
  ErrorHandlerMiddleware,
  BodyParserMiddleware,
  BodyValidationMiddleware,
} from '@noony/serverless';

// Define message schema
const messageSchema = z.object({
  userId: z.string().uuid(),
  action: z.enum(['CREATE', 'UPDATE', 'DELETE']),
  payload: z.record(z.unknown()),
});

type PubSubMessage = z.infer<typeof messageSchema>;

// Create Pub/Sub handler
const pubsubHandler = new Handler<PubSubMessage, unknown>()
  .use(new ErrorHandlerMiddleware())
  .use(new BodyParserMiddleware()) // Decodes base64 Pub/Sub messages
  .use(new BodyValidationMiddleware(messageSchema))
  .handle(async (context) => {
    const { action, payload } = context.req.validatedBody!;
    
    // Process message based on action
    switch (action) {
      case 'CREATE':
        await handleCreateAction(payload);
        break;
      case 'UPDATE':
        await handleUpdateAction(payload);
        break;
      case 'DELETE':
        await handleDeleteAction(payload);
        break;
    }
  });

// Export Cloud Function
export const processPubSubMessage = cloudEvent('processPubSubMessage', (cloudEvent) => {
  return pubsubHandler.execute(cloudEvent.data, {});
});

Built-in Middlewares

ErrorHandlerMiddleware

Centralized error handling with custom error types:

.use(new ErrorHandlerMiddleware())

// Handles these error types:
throw new HttpError(400, 'Bad Request');
throw new ValidationError('Invalid input');
throw new AuthenticationError('Unauthorized');

BodyParserMiddleware

Automatically parses JSON and Pub/Sub messages:

.use(new BodyParserMiddleware())
// Sets context.req.parsedBody

BodyValidationMiddleware

Zod schema validation with TypeScript integration:

const schema = z.object({ name: z.string() });
.use(new BodyValidationMiddleware(schema))
// Sets context.req.validatedBody with proper typing

AuthenticationMiddleware

JWT token verification:

const tokenVerifier = {
  async verifyToken(token: string) {
    // Your verification logic
    return { userId: '123', role: 'user' };
  }
};
.use(new AuthenticationMiddleware(tokenVerifier))
// Sets context.user

ResponseWrapperMiddleware

Standardized response format:

.use(new ResponseWrapperMiddleware())
// Wraps responses in: { success: true, payload: data, timestamp }

HeaderVariablesMiddleware

Validate required headers:

.use(new HeaderVariablesMiddleware(['authorization', 'content-type']))

QueryParametersMiddleware

Process query parameters:

.use(new QueryParametersMiddleware())
// Processes context.req.query

DependencyInjectionMiddleware

TypeDI container integration:

.use(new DependencyInjectionMiddleware([
  { id: 'userService', value: new UserService() }
]))

Error Handling

Built-in error classes with proper HTTP status codes:

// HTTP errors with custom status codes
throw new HttpError(400, 'Bad Request', 'INVALID_INPUT');

// Validation errors (400 status)
throw new ValidationError('Invalid email format', zodErrors);

// Authentication errors (401 status)
throw new AuthenticationError('Invalid token');

// Authorization errors (403 status) 
throw new AuthorizationError('Insufficient permissions');

Framework Integration

Google Cloud Functions

import { http } from '@google-cloud/functions-framework';

export const myFunction = http('myFunction', (req, res) => {
  return handler.execute(req, res);
});

Fastify Integration

import Fastify from 'fastify';
import { Handler } from '@noony/serverless';

const fastify = Fastify();

fastify.post('/users', async (request, reply) => {
  const req = { ...request, body: request.body };
  const res = {
    status: (code: number) => reply.status(code),
    json: (data: any) => reply.send(data)
  };
  
  await handler.execute(req, res);
});

Express Integration

import express from 'express';
import { Handler } from '@noony/serverless';

const app = express();

app.post('/users', async (req, res) => {
  await handler.execute(req, res);
});

Best Practices

1. Middleware Order

const handler = new Handler<RequestType, UserType>()
  .use(new ErrorHandlerMiddleware())        // Always first
  .use(new HeaderVariablesMiddleware(...))  // Required headers
  .use(new AuthenticationMiddleware(...))   // Authentication
  .use(new BodyParserMiddleware())          // Parse body
  .use(new BodyValidationMiddleware(...))   // Validate
  .use(new DependencyInjectionMiddleware(...))
  .use(new ResponseWrapperMiddleware())     // Always last
  .handle(async (context) => {
    // Business logic
  });

2. Type Safety

// Define clear interfaces
interface UserRequest {
  name: string;
  email: string;
}

interface UserContext {
  userId: string;
  role: string;
}

// Use throughout the handler
const handler = new Handler<UserRequest, UserContext>();

3. Error Handling

  • Always use ErrorHandlerMiddleware first
  • Throw appropriate error types
  • Handle errors gracefully in business logic
  • Use proper HTTP status codes

4. Testing

// Mock context for testing
const mockContext = {
  req: { validatedBody: { name: 'test' } },
  res: { json: jest.fn() },
  businessData: new Map(),
};

await handler.handle(mockContext);

TypeScript Support

The framework provides full type safety through generic types:

import {
  Handler,
  Context,
  BaseMiddleware,
  ErrorHandlerMiddleware,
  BodyValidationMiddleware,
} from '@noony/serverless';

// No type casting needed with proper generics
const handler = new Handler<UserRequest, UserContext>()
  .handle(async (context) => {
    // TypeScript knows validatedBody is UserRequest
    const { name, email } = context.req.validatedBody!;
    // TypeScript knows user is UserContext
    const { userId } = context.user!;
  });

Development Commands

npm run build          # Compile TypeScript
npm run watch          # Watch mode compilation  
npm run test           # Run Jest tests
npm run test:coverage  # Test with coverage
npm run lint           # ESLint check
npm run format         # Prettier formatting

Example API Usage

# Create user with authentication
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer valid-token" \
  -H "x-api-version: v1" \
  -d '{"name":"John Doe","email":"[email protected]","age":30}'

# Get user by ID
curl -H "Authorization: Bearer valid-token" \
  http://localhost:3000/api/users/123

# List users with query parameters
curl -H "Authorization: Bearer valid-token" \
  "http://localhost:3000/api/users?name=john"

Deployment

Google Cloud Functions

# Deploy HTTP function
gcloud functions deploy myFunction \
  --runtime nodejs20 \
  --trigger-http \
  --entry-point myFunction \
  --allow-unauthenticated

# Deploy Pub/Sub function
gcloud functions deploy myPubSubFunction \
  --runtime nodejs20 \
  --trigger-topic my-topic \
  --entry-point myPubSubFunction

Cloud Run

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 8080
CMD ["npm", "start"]

Publishing to npm

This package is published as @noony-serverless/core on npm. Follow these steps to publish a new version:

Prerequisites

  1. npm Account: You need an npm account. Create one at npmjs.com/signup
  2. Organization Access: You must have access to the @noony-serverless organization on npm
  3. Two-Factor Authentication: Highly recommended for security

Publishing Steps

1. Login to npm

npm login

You'll be prompted for:

  • Username
  • Password
  • Email
  • One-time password (if 2FA is enabled)

Verify you're logged in:

npm whoami

2. Prepare the Package

Ensure all changes are committed and tests pass:

# Run tests
npm test

# Run linter
npm run lint

# Build the package
npm run build

3. Update Version

Update the version in package.json following Semantic Versioning:

# For bug fixes (0.4.0 -> 0.4.1)
npm version patch

# For new features (0.4.0 -> 0.5.0)
npm version minor

# For breaking changes (0.4.0 -> 1.0.0)
npm version major

This will:

  • Update package.json version
  • Create a git commit
  • Create a git tag

4. Publish to npm

For scoped packages (like @noony-serverless/core), you must specify public access:

npm publish --access public

For the first publish only, you need the --access public flag. Subsequent publishes can use:

npm publish

5. Push to Git

Don't forget to push the version commit and tags:

git push && git push --tags

Publishing Checklist

Before publishing, verify:

  • [ ] All tests pass (npm test)
  • [ ] No linting errors (npm run lint)
  • [ ] Build succeeds (npm run build)
  • [ ] Version number updated in package.json
  • [ ] CHANGELOG.md updated (if applicable)
  • [ ] README.md is up to date
  • [ ] All changes committed to git
  • [ ] Logged into npm (npm whoami)

Troubleshooting

Error: "Access token expired or revoked"

Solution: Run npm login to re-authenticate

Error: "404 Not Found - Not in this registry"

Solution: For first publish of a scoped package, use:

npm publish --access public

Error: "You do not have permission to publish"

Solution:

  • Verify you're logged in as the correct user
  • Check you have publish access to the @noony-serverless organization
  • Create the organization if it doesn't exist

Error: "Cannot publish over existing version"

Solution: Update the version number in package.json or use:

npm version patch  # or minor/major

Error: "403 Forbidden"

Solutions:

  • Ensure you're logged in: npm whoami
  • Verify you own the package or have collaborator access
  • If this is a new scoped package, verify the organization exists

Automated Publishing with GitHub Actions

For automated publishing, create .github/workflows/publish.yml:

name: Publish to npm

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm test
      - run: npm run build
      - run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

To use this:

  1. Create an npm access token at npmjs.com/settings/tokens
  2. Add it as a GitHub secret named NPM_TOKEN
  3. Create a GitHub release to trigger publishing

Version Management

This package follows Semantic Versioning:

  • MAJOR version (1.0.0 → 2.0.0): Breaking changes
  • MINOR version (1.0.0 → 1.1.0): New features, backwards compatible
  • PATCH version (1.0.0 → 1.0.1): Bug fixes, backwards compatible

Current version: 0.4.0

Package Distribution

When published, the package includes only the build/ directory contents:

  • build/core/**/*.js and *.d.ts
  • build/middlewares/**/*.js and *.d.ts
  • build/utils/**/*.js and *.d.ts
  • build/index.js and index.d.ts
  • README.md

Source TypeScript files are not included in the npm package.

Community & Support

License

MIT License - see LICENSE file for details.