tagliatelle 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,488 @@
1
+ # 🍝 `<Tag>liatelle.js`
2
+
3
+ > **The Declarative Backend Framework.** Build APIs with JSX. Yes, really.
4
+
5
+ `<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.
6
+
7
+ **If you can write React, you can build a high-performance backend.**
8
+
9
+ ```tsx
10
+ import { render, Server, Logger, Cors, Routes } from 'tagliatelle';
11
+
12
+ const App = () => (
13
+ <Server port={3000}>
14
+ <Logger level="info" />
15
+ <Cors origin="*">
16
+ <Routes dir="./routes" />
17
+ </Cors>
18
+ </Server>
19
+ );
20
+
21
+ render(<App />);
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 🤔 The Origin Story
27
+
28
+ This project started as a **joke**.
29
+
30
+ 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**.
31
+
32
+ So I thought: *"If frontend devs want to write server code so badly, why not go all the way?"*
33
+
34
+ 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.
35
+
36
+ **Tagliatelle.js: Because if we're going to make everything look like PHP anyway, we might as well make it delicious.** 🍝
37
+
38
+ ### Why "Tagliatelle"?
39
+
40
+ - **`<Tag>`** — Because we write everything in JSX tags. `<Server>`, `<Route>`, `<Response>`... it's tags all the way down.
41
+ - **Tagliatelle** — It's pasta. Because frontend developers clearly want to write spaghetti code in the backend. 🍝
42
+
43
+ *At least this spaghetti is type-safe and al dente.*
44
+
45
+ ---
46
+
47
+ ## 🚀 Quick Start
48
+
49
+ ### Create a new project
50
+
51
+ ```bash
52
+ npx tagliatelle init my-api
53
+ cd my-api
54
+ npm install
55
+ npm run dev
56
+ ```
57
+
58
+ That's it! Your API is running at `http://localhost:3000` 🍝
59
+
60
+ ```bash
61
+ curl http://localhost:3000/health
62
+ # {"status":"Al Dente 🍝","timestamp":"..."}
63
+
64
+ curl http://localhost:3000/posts
65
+ # {"success":true,"count":2,"data":[...]}
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 🤌 Why `<Tag>liatelle.js`?
71
+
72
+ | Feature | Description |
73
+ |---------|-------------|
74
+ | **File-Based Routing** | Next.js-style routing — your file structure IS your API |
75
+ | **JSX Responses** | Return `<Response><Status code={201} /><Body data={...} /></Response>` |
76
+ | **JSX Middleware** | Use `<Err>` and `<Augment>` for clean auth flows |
77
+ | **JSX Config** | Configure routes with `<Logger>`, `<Middleware>`, `<RateLimiter>` |
78
+ | **Full TypeScript** | End-to-end type safety with `HandlerProps<TParams, TBody, TQuery>` |
79
+ | **Zero Boilerplate** | Handlers return data or JSX — no `res.send()` needed |
80
+ | **CLI Scaffolding** | `npx tagliatelle init` creates a ready-to-run project |
81
+
82
+ ---
83
+
84
+ ## 📦 Installation
85
+
86
+ ### New Project (Recommended)
87
+
88
+ ```bash
89
+ npx tagliatelle init my-api
90
+ ```
91
+
92
+ ### Add to Existing Project
93
+
94
+ ```bash
95
+ npm install tagliatelle
96
+ ```
97
+
98
+ Then configure your `tsconfig.json`:
99
+
100
+ ```json
101
+ {
102
+ "compilerOptions": {
103
+ "jsx": "react-jsx",
104
+ "jsxImportSource": "tagliatelle"
105
+ }
106
+ }
107
+ ```
108
+
109
+ ---
110
+
111
+ ## 📂 Project Structure
112
+
113
+ ```
114
+ my-api/
115
+ ├── server.tsx # Server entry point
116
+ ├── routes/ # File-based routing
117
+ │ ├── _config.tsx # Global route config
118
+ │ ├── index.tsx # GET /
119
+ │ ├── health.tsx # GET /health
120
+ │ └── posts/
121
+ │ ├── _config.tsx # Config for /posts/*
122
+ │ ├── _data.ts # Shared data (not a route)
123
+ │ ├── index.tsx # GET/POST /posts
124
+ │ └── [id].tsx # GET/PUT/DELETE /posts/:id
125
+ ├── middleware/
126
+ │ └── auth.tsx # JSX middleware
127
+ ├── tsconfig.json
128
+ └── package.json
129
+ ```
130
+
131
+ ---
132
+
133
+ ## 🍽️ Server Configuration
134
+
135
+ ### `server.tsx`
136
+
137
+ ```tsx
138
+ import path from 'node:path';
139
+ import { fileURLToPath } from 'node:url';
140
+ import { render, Server, Logger, Cors, Routes } from 'tagliatelle';
141
+
142
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
143
+
144
+ const App = () => (
145
+ <Server port={3000}>
146
+ <Logger level="info" />
147
+ <Cors origin="*">
148
+ <Routes dir={path.join(__dirname, 'routes')} />
149
+ </Cors>
150
+ </Server>
151
+ );
152
+
153
+ render(<App />);
154
+ ```
155
+
156
+ ### Server Components
157
+
158
+ | Component | Description |
159
+ |-----------|-------------|
160
+ | `<Server port={3000}>` | Main server wrapper |
161
+ | `<Logger level="info" />` | Configure logging level |
162
+ | `<Cors origin="*">` | Enable CORS |
163
+ | `<Routes dir="./routes" />` | Load file-based routes |
164
+ | `<RateLimiter max={100} timeWindow="1 minute" />` | Rate limiting |
165
+ | `<Middleware use={fn} />` | Add global middleware |
166
+
167
+ ---
168
+
169
+ ## 📁 File-Based Routing
170
+
171
+ Your file structure becomes your API:
172
+
173
+ | File | Route |
174
+ |------|-------|
175
+ | `routes/index.tsx` | `GET /` |
176
+ | `routes/health.tsx` | `GET /health` |
177
+ | `routes/posts/index.tsx` | `GET/POST /posts` |
178
+ | `routes/posts/[id].tsx` | `GET/PUT/DELETE /posts/:id` |
179
+ | `routes/users/[id]/posts.tsx` | `GET /users/:id/posts` |
180
+
181
+ ### Route File Example
182
+
183
+ ```tsx
184
+ // routes/posts/[id].tsx
185
+ import { Response, Status, Body, Err } from 'tagliatelle';
186
+ import type { HandlerProps } from 'tagliatelle';
187
+
188
+ interface PostParams { id: string }
189
+
190
+ export async function GET({ params, log }: HandlerProps<PostParams>) {
191
+ log.info(`Fetching post ${params.id}`);
192
+
193
+ const post = await db.posts.find(params.id);
194
+
195
+ if (!post) {
196
+ return <Err code={404} message="Post not found" />;
197
+ }
198
+
199
+ return (
200
+ <Response>
201
+ <Status code={200} />
202
+ <Body data={{ success: true, data: post }} />
203
+ </Response>
204
+ );
205
+ }
206
+
207
+ export async function DELETE({ params }: HandlerProps<PostParams>) {
208
+ await db.posts.delete(params.id);
209
+
210
+ return (
211
+ <Response>
212
+ <Status code={200} />
213
+ <Body data={{ success: true, message: "Deleted" }} />
214
+ </Response>
215
+ );
216
+ }
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 🎛️ Route Configuration
222
+
223
+ Create `_config.tsx` files to configure routes per directory:
224
+
225
+ ### `routes/_config.tsx` (Global)
226
+
227
+ ```tsx
228
+ import { Logger } from 'tagliatelle';
229
+
230
+ export default () => (
231
+ <>
232
+ <Logger level="info" />
233
+ </>
234
+ );
235
+ ```
236
+
237
+ ### `routes/posts/_config.tsx` (Posts-specific)
238
+
239
+ ```tsx
240
+ import { Logger, Middleware, RateLimiter } from 'tagliatelle';
241
+ import type { HandlerProps } from 'tagliatelle';
242
+ import { authMiddleware } from '../middleware/auth.js';
243
+
244
+ // Only require auth for write operations
245
+ const writeAuthMiddleware = async (props: HandlerProps, request, reply) => {
246
+ if (['GET', 'HEAD', 'OPTIONS'].includes(request.method)) {
247
+ return; // Skip auth for reads
248
+ }
249
+ return authMiddleware(props, request, reply);
250
+ };
251
+
252
+ export default () => (
253
+ <>
254
+ <Logger level="debug" />
255
+ <RateLimiter max={100} timeWindow="1 minute" />
256
+ <Middleware use={writeAuthMiddleware} />
257
+ </>
258
+ );
259
+ ```
260
+
261
+ ### Config Inheritance
262
+
263
+ Configs are **inherited** and **merged**:
264
+ - Child configs override parent settings
265
+ - Middleware is **additive** (stacks)
266
+ - Nested directories inherit from parents
267
+
268
+ ---
269
+
270
+ ## 📤 JSX Responses
271
+
272
+ Return beautiful, declarative responses:
273
+
274
+ ```tsx
275
+ // Success response
276
+ return (
277
+ <Response>
278
+ <Status code={201} />
279
+ <Body data={{
280
+ success: true,
281
+ message: "Created!",
282
+ data: newItem
283
+ }} />
284
+ </Response>
285
+ );
286
+
287
+ // Error response (shorthand)
288
+ return <Err code={404} message="Not found" />;
289
+
290
+ // With custom headers
291
+ return (
292
+ <Response>
293
+ <Status code={200} />
294
+ <Headers headers={{ 'X-Custom': 'value' }} />
295
+ <Body data={result} />
296
+ </Response>
297
+ );
298
+ ```
299
+
300
+ ### Response Components
301
+
302
+ | Component | Description |
303
+ |-----------|-------------|
304
+ | `<Response>` | Wrapper for composing responses |
305
+ | `<Status code={201} />` | Set HTTP status code |
306
+ | `<Body data={{...}} />` | Set JSON response body |
307
+ | `<Headers headers={{...}} />` | Set custom headers |
308
+ | `<Err code={404} message="..." />` | Error response shorthand |
309
+
310
+ ---
311
+
312
+ ## 🌶️ Middleware
313
+
314
+ Middleware can use JSX components for responses and prop augmentation!
315
+
316
+ ### Creating Middleware
317
+
318
+ ```tsx
319
+ // middleware/auth.tsx
320
+ import { Augment, Err, authFailureTracker, isSafeString } from 'tagliatelle';
321
+ import type { HandlerProps, MiddlewareFunction } from 'tagliatelle';
322
+
323
+ export const authMiddleware: MiddlewareFunction = async (props, request, reply) => {
324
+ const apiKey = request.headers['x-api-key'];
325
+
326
+ // Return JSX error response
327
+ if (!apiKey || typeof apiKey !== 'string') {
328
+ return <Err code={401} message="Authentication required" />;
329
+ }
330
+
331
+ const user = await verifyToken(apiKey);
332
+
333
+ if (!user) {
334
+ return <Err code={401} message="Invalid credentials" />;
335
+ }
336
+
337
+ // Augment props with user data
338
+ return <Augment user={user} />;
339
+ };
340
+ ```
341
+
342
+ ### Middleware Factory Pattern
343
+
344
+ ```tsx
345
+ // Role-based authorization factory
346
+ export function requireRole(role: string): MiddlewareFunction {
347
+ return async (props, request, reply) => {
348
+ const user = props.user;
349
+
350
+ if (!user || user.role !== role) {
351
+ return <Err code={403} message="Access denied" />;
352
+ }
353
+
354
+ return; // Continue to handler
355
+ };
356
+ }
357
+
358
+ // Usage in _config.tsx
359
+ <Middleware use={requireRole('admin')} />
360
+ ```
361
+
362
+ ### Middleware Components
363
+
364
+ | Component | Description |
365
+ |-----------|-------------|
366
+ | `<Err code={401} message="..." />` | Return error and halt chain |
367
+ | `<Augment user={...} />` | Add data to handler props |
368
+
369
+ ---
370
+
371
+ ## ⚙️ Handler Props
372
+
373
+ Every handler receives typed props:
374
+
375
+ ```tsx
376
+ interface HandlerProps<TParams, TBody, TQuery> {
377
+ params: TParams; // URL parameters
378
+ query: TQuery; // Query string
379
+ body: TBody; // Request body
380
+ headers: Record<string, string>; // Request headers
381
+ request: FastifyRequest; // Raw Fastify request
382
+ reply: FastifyReply; // Raw Fastify reply
383
+ log: Logger; // Fastify logger
384
+ user?: unknown; // From auth middleware
385
+ db?: unknown; // From DB provider
386
+ }
387
+ ```
388
+
389
+ ### Example with Types
390
+
391
+ ```tsx
392
+ interface CreatePostBody {
393
+ title: string;
394
+ content: string;
395
+ }
396
+
397
+ export async function POST({ body, user, log }: HandlerProps<unknown, CreatePostBody>) {
398
+ log.info('Creating post');
399
+
400
+ if (!body.title) {
401
+ return <Err code={400} message="Title required" />;
402
+ }
403
+
404
+ const post = await createPost({ ...body, author: user.id });
405
+
406
+ return (
407
+ <Response>
408
+ <Status code={201} />
409
+ <Body data={{ success: true, data: post }} />
410
+ </Response>
411
+ );
412
+ }
413
+ ```
414
+
415
+ ---
416
+
417
+ ## 📜 Naming Conventions
418
+
419
+ | Pattern | Description |
420
+ |---------|-------------|
421
+ | `index.tsx` | Root of directory (`/posts/index.tsx` → `/posts`) |
422
+ | `[param].tsx` | Dynamic parameter (`/posts/[id].tsx` → `/posts/:id`) |
423
+ | `[...slug].tsx` | Catch-all (`/docs/[...slug].tsx` → `/docs/*`) |
424
+ | `_config.tsx` | Directory configuration (not a route) |
425
+ | `_*.ts` | Private files (ignored by router) |
426
+
427
+ ---
428
+
429
+ ## 🛡️ Security Utilities
430
+
431
+ Tagliatelle includes security helpers:
432
+
433
+ ```tsx
434
+ import {
435
+ authFailureTracker, // Rate limit auth failures by IP
436
+ isSafeString, // Validate string safety
437
+ sanitizeErrorMessage, // Clean error messages
438
+ safeErrorResponse, // Safe error responses
439
+ withTimeout, // Add timeouts to async operations
440
+ } from 'tagliatelle';
441
+ ```
442
+
443
+ ---
444
+
445
+ ## 🚀 Performance
446
+
447
+ Built on Fastify, you get:
448
+ - **100k+ requests/second** throughput
449
+ - **Low latency** JSON serialization
450
+ - **Schema validation** support
451
+ - **Automatic logging**
452
+
453
+ ---
454
+
455
+ ## 📋 CLI Reference
456
+
457
+ ```bash
458
+ # Create a new project
459
+ npx tagliatelle init my-api
460
+
461
+ # Create without installing dependencies
462
+ npx tagliatelle init my-api --skip-install
463
+
464
+ # Show help
465
+ npx tagliatelle --help
466
+ ```
467
+
468
+ ---
469
+
470
+ ## 🤝 Contributing
471
+
472
+ Got a new "ingredient"? Open a Pull Request! We're looking for:
473
+
474
+ - [ ] WebSocket support (`<WebSocket />`)
475
+ - [ ] OpenAPI schema generation
476
+ - [ ] Static file serving (`<Static />`)
477
+ - [ ] GraphQL integration
478
+ - [ ] Database adapters
479
+
480
+ ---
481
+
482
+ ## 📜 License
483
+
484
+ MIT
485
+
486
+ ---
487
+
488
+ **Made with ❤️ and plenty of carbs. Chahya Tayba !** 🇹🇳
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * 🍝 Tagliatelle CLI
4
+ *
5
+ * Create new Tagliatelle projects with a single command!
6
+ *
7
+ * Usage:
8
+ * npx tagliatelle init my-api
9
+ * npx tagliatelle init my-api --skip-install
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG"}