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 πŸ™

Β© 2025 – Pkg Stats / Ryan Hefner

tagliatelle

v1.0.0-beta.8

Published

🍝 The Declarative Backend Framework - JSX-powered API architecture on Fastify

Downloads

708

Readme

🍝 <Tag>liatelle.js

npm version npm downloads license

The Declarative Backend Framework. Build APIs with JSX. Yes, really.

πŸ“š Live Documentation | πŸ“¦ NPM Package

⚠️ Status: This is an experimental project. Most features are not tested or partially tested. Use at your own risk in production. Contributions and bug reports are welcome!

<Tag>liatelle.js is a TypeScript backend framework built on top of Fastify that treats your API architecture like a component tree. Using JSX/TSX, you define your routes, middleware, and responses as a visual hierarchy.

If you can write React, you can build a high-performance backend.

import { render, Server, Logger, Cors, Routes } from 'tagliatelle';
import { Swagger } from './plugins/swagger.js';

const App = () => (
  <Server port={3000}>
    <Swagger title="My API" version="1.0.0" />
    <Logger level="info" />
    <Cors origin="*">
      <Routes dir="./routes" />
    </Cors>
  </Server>
);

render(<App />);

πŸ€” The Origin Story

This project started as a joke.

I noticed that every frontend framework is racing to become more server-oriented. React added Server Components. Next.js gave us "use server". Remix is basically a backend framework wearing a React costume. The JavaScript ecosystem is slowly but surely... becoming PHP.

So I thought: "If frontend devs want to write server code so badly, why not go all the way?"

Instead of sneaking server code into your React components, let's do the opposite β€” write your entire backend in pure TSX. Routes? JSX. Middleware? JSX. Responses? You guessed it... JSX.

Tagliatelle.js: Because if we're going to make everything look like PHP anyway, we might as well make it delicious. 🍝

Why "Tagliatelle"?

  • <Tag> β€” Because we write everything in JSX tags. <Server>, <Route>, <Response>... it's tags all the way down.
  • Tagliatelle β€” It's pasta. Because frontend developers clearly want to write spaghetti code in the backend. 🍝

At least this spaghetti is type-safe and al dente.


πŸš€ Quick Start

Create a new project

npx tagliatelle@beta init my-api
cd my-api
npm run dev

That's it! Your API is running at http://localhost:3000 🍝

curl http://localhost:3000/health
# {"status":"Al Dente 🍝","timestamp":"..."}

curl http://localhost:3000/posts
# {"success":true,"count":2,"data":[...]}

🀌 Why <Tag>liatelle.js?

| Feature | Description | |---------|-------------| | File-Based Routing | Next.js-style routing β€” your file structure IS your API | | JSX Responses | Return <Response><Status code={201} /><Body data={...} /></Response> | | JSX Middleware | Use <Err> and <Augment> for clean auth flows | | JSX Config | Configure routes with <Logger>, <Middleware>, <RateLimiter> | | Plugin System | Create custom tags with createPlugin β€” add Swagger, GraphQL, WebSockets, anything! | | OpenAPI Schemas | Export GET_SCHEMA, POST_SCHEMA for auto-generated docs | | Full TypeScript | End-to-end type safety with HandlerProps<TParams, TBody, TQuery> | | Zero Boilerplate | Handlers return data or JSX β€” no res.send() needed | | CLI Scaffolding | npx tagliatelle@beta init creates a ready-to-run project |


πŸ“¦ Installation

New Project (Recommended)

npx tagliatelle@beta init my-api

Add to Existing Project

npm install tagliatelle@beta

Then configure your tsconfig.json:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "tagliatelle"
  }
}

πŸ“‚ Project Structure

my-api/
β”œβ”€β”€ server.tsx              # Server entry point
β”œβ”€β”€ routes/                 # File-based routing
β”‚   β”œβ”€β”€ _config.tsx         # Global route config
β”‚   β”œβ”€β”€ index.tsx           # GET /
β”‚   β”œβ”€β”€ health.tsx          # GET /health
β”‚   β”œβ”€β”€ auth/               # Auth routes
β”‚   β”‚   β”œβ”€β”€ login.tsx       # POST /auth/login
β”‚   β”‚   β”œβ”€β”€ register.tsx    # POST /auth/register
β”‚   β”‚   └── me.tsx          # GET /auth/me
β”‚   └── posts/
β”‚       β”œβ”€β”€ _config.tsx     # Config for /posts/*
β”‚       β”œβ”€β”€ index.tsx       # GET/POST /posts
β”‚       └── [id].tsx        # GET/PUT/DELETE /posts/:id
β”œβ”€β”€ plugins/                # Custom plugins
β”‚   └── swagger.tsx         # Swagger integration
β”œβ”€β”€ databases/              # Database providers
β”‚   └── contentDB.ts        # Content database
β”œβ”€β”€ middleware/
β”‚   └── auth.tsx            # JSX middleware
β”œβ”€β”€ tsconfig.json
└── package.json

Examples Folder (This Repo)

The examples/ folder contains a comprehensive demo showing all features:

examples/
β”œβ”€β”€ server.tsx              # Multi-database server demo
β”œβ”€β”€ routes/                 # Complete route examples
β”‚   β”œβ”€β”€ auth/               # Authentication routes
β”‚   β”œβ”€β”€ posts/              # Content CRUD
β”‚   β”œβ”€β”€ categories/         # Categories
β”‚   β”œβ”€β”€ tags/               # Tags
β”‚   β”œβ”€β”€ search/             # Search
β”‚   └── pages/              # HTML pages (docs site source)
β”œβ”€β”€ plugins/                # Plugin examples
β”‚   β”œβ”€β”€ swagger.tsx         # OpenAPI documentation
β”‚   β”œβ”€β”€ websocket.tsx       # WebSocket support
β”‚   β”œβ”€β”€ graphql.tsx         # GraphQL integration
β”‚   β”œβ”€β”€ metrics.tsx         # Prometheus metrics
β”‚   └── redis.tsx           # Redis caching
β”œβ”€β”€ databases/              # Multi-database setup
β”‚   β”œβ”€β”€ authDB.ts           # Auth database
β”‚   └── contentDB.ts        # Content database
β”œβ”€β”€ scripts/
β”‚   └── build-docs.cjs      # Build static docs for GitHub Pages
└── docs/                   # Generated static site
    β”œβ”€β”€ index.html          # Landing page
    └── docs.html           # Documentation

Run the examples:

npm run example        # Start server
npm run example:dev    # Start with hot reload

🍽️ Server Configuration

server.tsx

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { render, Server, Logger, Cors, Routes } from 'tagliatelle';
import { Swagger } from './plugins/swagger.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const App = () => (
  <Server port={3000}>
    {/* Custom plugins! */}
    <Swagger title="My API" version="1.0.0" path="/docs" />
    
    <Logger level="info" />
    <Cors origin="*">
      <Routes dir={path.join(__dirname, 'routes')} />
    </Cors>
  </Server>
);

render(<App />);

Server Components

| Component | Description | |-----------|-------------| | <Server port={3000}> | Main server wrapper | | <Logger level="info" /> | Configure logging level | | <Cors origin="*"> | Enable CORS | | <Routes dir="./routes" /> | Load file-based routes | | <RateLimiter max={100} timeWindow="1 minute" /> | Rate limiting | | <Middleware use={fn} /> | Add global middleware |

Runtime Options

You can override server settings at runtime using CLI flags or environment variables:

| Option | Env Variable | Description | |--------|--------------|-------------| | -p, --port <number> | PORT | Port to listen on (default: 3000) | | -H, --host <string> | HOST | Host to bind to (default: 0.0.0.0) | | -o, --open | β€” | Open browser after server starts | | -h, --help | β€” | Show help message |

Priority: CLI flags > Environment variables > Code defaults


πŸ”Œ Plugin System (Custom Tags)

Create your own JSX components that hook into Fastify! Perfect for integrating third-party libraries.

Creating a Plugin

// plugins/swagger.tsx
import { createPlugin } from 'tagliatelle';
import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';

interface SwaggerProps {
  title?: string;
  version?: string;
  path?: string;
}

export const Swagger = createPlugin<SwaggerProps>(
  'Swagger',  // Plugin name (for logging)
  async (fastify, props, config) => {
    // You have full access to Fastify!
    await fastify.register(swagger, {
      openapi: {
        info: {
          title: props.title ?? 'API',
          version: props.version ?? '1.0.0'
        }
      }
    });
    
    await fastify.register(swaggerUi, {
      routePrefix: props.path ?? '/docs'
    });
  }
);

Using Your Plugin

import { Swagger } from './plugins/swagger.js';

const App = () => (
  <Server port={3000}>
    <Swagger title="My API" version="1.0.0" path="/docs" />
    <Routes dir="./routes" />
  </Server>
);

Plugin Examples

Here are plugins you can create:

GraphQL

import { createPlugin } from 'tagliatelle';

export const GraphQL = createPlugin<{ schema: GraphQLSchema }>(
  'GraphQL',
  async (fastify, props) => {
    const mercurius = await import('mercurius');
    await fastify.register(mercurius.default, {
      schema: props.schema,
      graphiql: true
    });
  }
);

// Usage: <GraphQL schema={mySchema} />

WebSocket

import { createPlugin } from 'tagliatelle';

export const WebSocket = createPlugin<{ path?: string }>(
  'WebSocket',
  async (fastify, props) => {
    const ws = await import('@fastify/websocket');
    await fastify.register(ws.default);
    
    fastify.get(props.path ?? '/ws', { websocket: true }, (socket) => {
      socket.on('message', (msg) => socket.send(`Echo: ${msg}`));
    });
  }
);

// Usage: <WebSocket path="/ws" />

Prometheus Metrics

import { createPlugin } from 'tagliatelle';

export const Metrics = createPlugin<{ path?: string }>(
  'Metrics',
  async (fastify, props) => {
    const metrics = await import('fastify-metrics');
    await fastify.register(metrics.default, {
      endpoint: props.path ?? '/metrics'
    });
  }
);

// Usage: <Metrics path="/metrics" />

Redis Cache

import { createPlugin } from 'tagliatelle';

export const Redis = createPlugin<{ url: string }>(
  'Redis',
  async (fastify, props) => {
    const redis = await import('@fastify/redis');
    await fastify.register(redis.default, { url: props.url });
  }
);

// Usage: <Redis url="redis://localhost:6379" />

Plugin API

createPlugin<TProps>(
  name: string,
  handler: (fastify, props, config) => Promise<void>
)

| Parameter | Type | Description | |-----------|------|-------------| | name | string | Plugin name for logging | | handler | PluginHandler | Async function that receives Fastify instance | | fastify | FastifyInstance | Full access to register plugins, add routes, etc. | | props | TProps | Props passed to the JSX component | | config | RouteConfig | Current route configuration (middleware, prefix, etc.) |


πŸ“ File-Based Routing

Your file structure becomes your API:

| File | Route | |------|-------| | routes/index.tsx | GET / | | routes/health.tsx | GET /health | | routes/posts/index.tsx | GET/POST /posts | | routes/posts/[id].tsx | GET/PUT/DELETE /posts/:id | | routes/users/[id]/posts.tsx | GET /users/:id/posts |

Route File Example

// routes/posts/[id].tsx
import { Response, Status, Body, Err } from 'tagliatelle';
import type { HandlerProps } from 'tagliatelle';

interface PostParams { id: string }

export async function GET({ params, log }: HandlerProps<PostParams>) {
  log.info(`Fetching post ${params.id}`);
  
  const post = await db.posts.find(params.id);
  
  if (!post) {
    return <Err code={404} message="Post not found" />;
  }
  
  return (
    <Response>
      <Status code={200} />
      <Body data={{ success: true, data: post }} />
    </Response>
  );
}

export async function DELETE({ params }: HandlerProps<PostParams>) {
  await db.posts.delete(params.id);
  
  return (
    <Response>
      <Status code={200} />
      <Body data={{ success: true, message: "Deleted" }} />
    </Response>
  );
}

πŸ“š OpenAPI Schema Support

Export schemas alongside your handlers for automatic OpenAPI documentation:

// routes/posts/index.tsx
import { Response, Status, Body, Err } from 'tagliatelle';
import type { HandlerProps } from 'tagliatelle';

// ✨ Export schemas for OpenAPI/Swagger
export const GET_SCHEMA = {
  summary: 'List all posts',
  description: 'Returns a paginated list of blog posts',
  tags: ['posts'],
  response: {
    200: {
      type: 'object',
      properties: {
        success: { type: 'boolean' },
        count: { type: 'number' },
        data: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              id: { type: 'string' },
              title: { type: 'string' },
              content: { type: 'string' }
            }
          }
        }
      }
    }
  }
};

export const POST_SCHEMA = {
  summary: 'Create a new post',
  tags: ['posts'],
  body: {
    type: 'object',
    required: ['title', 'content'],
    properties: {
      title: { type: 'string', description: 'Post title' },
      content: { type: 'string', description: 'Post content' }
    }
  },
  response: {
    201: {
      type: 'object',
      properties: {
        success: { type: 'boolean' },
        data: { type: 'object' }
      }
    }
  }
};

// Your handlers
export async function GET({ log }: HandlerProps) {
  const posts = await db.posts.findAll();
  return (
    <Response>
      <Status code={200} />
      <Body data={{ success: true, count: posts.length, data: posts }} />
    </Response>
  );
}

export async function POST({ body }: HandlerProps<unknown, CreatePostBody>) {
  const post = await db.posts.create(body);
  return (
    <Response>
      <Status code={201} />
      <Body data={{ success: true, data: post }} />
    </Response>
  );
}

Schema Naming Convention

| Export Name | HTTP Method | |-------------|-------------| | GET_SCHEMA | GET | | POST_SCHEMA | POST | | PUT_SCHEMA | PUT | | DELETE_SCHEMA | DELETE | | PATCH_SCHEMA | PATCH |

These schemas are automatically picked up by Swagger and other OpenAPI tools!


πŸŽ›οΈ Route Configuration

Create _config.tsx files to configure routes per directory:

routes/_config.tsx (Global)

import { Logger } from 'tagliatelle';

export default () => (
  <>
    <Logger level="info" />
  </>
);

routes/posts/_config.tsx (Posts-specific)

import { Logger, Middleware, RateLimiter } from 'tagliatelle';
import type { HandlerProps } from 'tagliatelle';
import { authMiddleware } from '../middleware/auth.js';

// Only require auth for write operations
const writeAuthMiddleware = async (props: HandlerProps, request, reply) => {
  if (['GET', 'HEAD', 'OPTIONS'].includes(request.method)) {
    return; // Skip auth for reads
  }
  return authMiddleware(props, request, reply);
};

export default () => (
  <>
    <Logger level="debug" />
    <RateLimiter max={100} timeWindow="1 minute" />
    <Middleware use={writeAuthMiddleware} />
  </>
);

Config Inheritance

Configs are inherited and merged:

  • Child configs override parent settings
  • Middleware is additive (stacks)
  • Nested directories inherit from parents

πŸ“€ JSX Responses

Return beautiful, declarative responses:

// Success response
return (
  <Response>
    <Status code={201} />
    <Body data={{ 
      success: true, 
      message: "Created!",
      data: newItem 
    }} />
  </Response>
);

// Error response (shorthand)
return <Err code={404} message="Not found" />;

// With custom headers
return (
  <Response>
    <Status code={200} />
    <Headers headers={{ 'X-Custom': 'value' }} />
    <Body data={result} />
  </Response>
);

Response Components

| Component | Description | |-----------|-------------| | <Response> | Wrapper for composing responses | | <Status code={201} /> | Set HTTP status code | | <Body data={{...}} /> | Set JSON response body | | <Headers headers={{...}} /> | Set custom headers | | <Err code={404} message="..." /> | Error response shorthand |


🌢️ Middleware

Middleware can use JSX components for responses and prop augmentation!

Creating Middleware

// middleware/auth.tsx
import { Augment, Err, authFailureTracker, isSafeString } from 'tagliatelle';
import type { HandlerProps, MiddlewareFunction } from 'tagliatelle';

export const authMiddleware: MiddlewareFunction = async (props, request, reply) => {
  const apiKey = request.headers['x-api-key'];
  
  // Return JSX error response
  if (!apiKey || typeof apiKey !== 'string') {
    return <Err code={401} message="Authentication required" />;
  }
  
  const user = await verifyToken(apiKey);
  
  if (!user) {
    return <Err code={401} message="Invalid credentials" />;
  }
  
  // Augment props with user data
  return <Augment user={user} />;
};

Middleware Factory Pattern

// Role-based authorization factory
export function requireRole(role: string): MiddlewareFunction {
  return async (props, request, reply) => {
    const user = props.user;
    
    if (!user || user.role !== role) {
      return <Err code={403} message="Access denied" />;
    }
    
    return; // Continue to handler
  };
}

// Usage in _config.tsx
<Middleware use={requireRole('admin')} />

Middleware Components

| Component | Description | |-----------|-------------| | <Err code={401} message="..." /> | Return error and halt chain | | <Augment user={...} /> | Add data to handler props |


βš™οΈ Handler Props

Every handler receives typed props:

interface HandlerProps<TParams, TBody, TQuery> {
  params: TParams;                    // URL parameters
  query: TQuery;                      // Query string
  body: TBody;                        // Request body
  headers: Record<string, string>;    // Request headers
  request: FastifyRequest;            // Raw Fastify request
  reply: FastifyReply;                // Raw Fastify reply
  log: Logger;                        // Fastify logger
  user?: unknown;                     // From auth middleware
  db?: unknown;                       // From DB provider
}

Example with Types

interface CreatePostBody {
  title: string;
  content: string;
}

export async function POST({ body, user, log }: HandlerProps<unknown, CreatePostBody>) {
  log.info('Creating post');
  
  if (!body.title) {
    return <Err code={400} message="Title required" />;
  }
  
  const post = await createPost({ ...body, author: user.id });
  
  return (
    <Response>
      <Status code={201} />
      <Body data={{ success: true, data: post }} />
    </Response>
  );
}

πŸ“œ Naming Conventions

| Pattern | Description | |---------|-------------| | index.tsx | Root of directory (/posts/index.tsx β†’ /posts) | | [param].tsx | Dynamic parameter (/posts/[id].tsx β†’ /posts/:id) | | [...slug].tsx | Catch-all (/docs/[...slug].tsx β†’ /docs/*) | | _config.tsx | Directory configuration (not a route) | | _*.ts | Private files (ignored by router) |


πŸ›‘οΈ Security Utilities

Tagliatelle includes security helpers:

import { 
  authFailureTracker,   // Rate limit auth failures by IP
  isSafeString,         // Validate string safety
  sanitizeErrorMessage, // Clean error messages
  safeErrorResponse,    // Safe error responses
  withTimeout,          // Add timeouts to async operations
} from 'tagliatelle';

πŸš€ Performance

Built on Fastify, you get:

  • 100k+ requests/second throughput
  • Low latency JSON serialization
  • Schema validation support
  • Automatic logging

πŸ“‹ CLI Reference

# Create a new project
npx tagliatelle@beta init my-api

# Development with hot reload
npm run dev

# Production mode  
npm run start

# Show runtime options (port, host, etc.)
npm run dev -- --help

πŸ”— Full Example

Here's a complete example with plugins, schemas, and middleware:

// server.tsx
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { render, Server, Logger, Cors, RateLimiter, Routes } from 'tagliatelle';
import { Swagger } from './plugins/swagger.js';
import { Metrics } from './plugins/metrics.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const App = () => (
  <Server port={3000}>
    {/* Plugins */}
    <Swagger 
      title="My API" 
      version="1.0.0"
      description="A delicious API"
      tags={[
        { name: 'posts', description: 'Blog post operations' },
        { name: 'users', description: 'User management' }
      ]}
    />
    <Metrics path="/metrics" />
    
    {/* Configuration */}
    <Logger level="info" />
    <Cors origin="*">
      <RateLimiter max={1000} timeWindow="1 minute">
        <Routes dir={path.join(__dirname, 'routes')} />
      </RateLimiter>
    </Cors>
  </Server>
);

render(<App />);

🀝 Contributing

Got a new "ingredient"? Open a Pull Request! With the plugin system, you can now contribute:

  • [ ] Official plugin packages (@tagliatelle/swagger, @tagliatelle/graphql, etc.)
  • [ ] More example plugins
  • [ ] Documentation improvements
  • [ ] Type improvements
  • [ ] Performance optimizations

πŸ“œ License

MIT


🎭 Disclaimer

This project started as a joke. And honestly? It still is.

But here's the thing β€” it actually works. You can build real APIs with it. The JSX compiles, the routes register, the middleware chains, and Fastify does its thing underneath.

Is it production-ready? Probably.
Is it a good idea? Debatable.
Is it fun? Absolutely.

Think of Tagliatelle.js as that friend who shows up to a formal dinner in a pasta costume β€” technically dressed, surprisingly functional, and definitely memorable.

Don't use it for:

  • πŸš€ NASA mission control systems
  • 🏦 Building Banks infra
  • Semiconductors SOftware

Made with ❀️ and plenty of carbs. Chahya Tayba ! πŸ‡ΉπŸ‡³