next-api-layer 0.1.5

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,743 @@
1
+ # next-api-layer
2
+
3
+ > Production-grade API layer for Next.js + External JWT Backend (Laravel, Django, .NET, Go, Express)
4
+
5
+ [![npm](https://img.shields.io/npm/v/next-api-layer)](https://www.npmjs.com/package/next-api-layer)
6
+ [![GitHub](https://img.shields.io/github/license/dijinar/next-api-layer)](https://github.com/dijinar/next-api-layer)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org)
8
+ [![Next.js](https://img.shields.io/badge/Next.js-14+-black)](https://nextjs.org)
9
+
10
+ ## The Problem
11
+
12
+ Building Next.js apps with external JWT backends (not NextAuth/Clerk) requires:
13
+ - Token validation middleware
14
+ - Guest token handling
15
+ - XSS sanitization
16
+ - i18n support
17
+ - Cookie management with httpOnly
18
+ - Token refresh with caching
19
+ - ...and 15+ other concerns
20
+
21
+ **This library solves all of them in one package.**
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install next-api-layer
27
+ # or
28
+ pnpm add next-api-layer
29
+ # or
30
+ yarn add next-api-layer
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ### 1. Create the Auth Proxy (Middleware)
36
+
37
+ ```ts
38
+ // middleware.ts
39
+ import { createAuthProxy } from 'next-api-layer';
40
+
41
+ const authProxy = createAuthProxy({
42
+ apiBaseUrl: process.env.API_BASE_URL!,
43
+ cookies: {
44
+ user: 'userAuthToken',
45
+ guest: 'guestAuthToken',
46
+ },
47
+ guestToken: {
48
+ enabled: true,
49
+ credentials: {
50
+ username: process.env.GUEST_USERNAME!,
51
+ password: process.env.GUEST_PASSWORD!,
52
+ },
53
+ },
54
+ access: {
55
+ protectedRoutes: ['/dashboard', '/profile', '/settings'],
56
+ authRoutes: ['/login', '/register'],
57
+ },
58
+ });
59
+
60
+ export default authProxy;
61
+
62
+ export const config = {
63
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
64
+ };
65
+ ```
66
+
67
+ #### Custom Middleware (Composable)
68
+
69
+ Kendi middleware lojiğinizi eklemek istiyorsanız `beforeAuth` ve `afterAuth` hook'larını kullanabilirsiniz:
70
+
71
+ ```ts
72
+ // middleware.ts
73
+ import { createAuthProxy } from 'next-api-layer';
74
+ import { NextRequest, NextResponse } from 'next/server';
75
+
76
+ const authProxy = createAuthProxy({
77
+ apiBaseUrl: process.env.API_BASE_URL!,
78
+ cookies: {
79
+ user: 'userAuthToken',
80
+ guest: 'guestAuthToken',
81
+ },
82
+
83
+ // Auth kontrolünden ÖNCE çalışır
84
+ beforeAuth: async (req: NextRequest) => {
85
+ const { pathname } = req.nextUrl;
86
+
87
+ // Rate limiting
88
+ if (pathname.startsWith('/api/') && isRateLimited(req)) {
89
+ return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
90
+ }
91
+
92
+ // Maintenance mode
93
+ if (process.env.MAINTENANCE_MODE === 'true' && !pathname.startsWith('/maintenance')) {
94
+ return NextResponse.redirect(new URL('/maintenance', req.url));
95
+ }
96
+
97
+ // Logging
98
+ console.log(`[${new Date().toISOString()}] ${req.method} ${pathname}`);
99
+
100
+ // null döndür = auth kontrolüne devam et
101
+ return null;
102
+ },
103
+
104
+ // Auth kontrolünden SONRA çalışır
105
+ afterAuth: async (req, response, authResult) => {
106
+ // Custom header ekle
107
+ response.headers.set('x-auth-status', authResult.isAuthenticated ? 'authenticated' : 'guest');
108
+
109
+ // Admin kontrolü
110
+ if (req.nextUrl.pathname.startsWith('/admin') && authResult.user?.role !== 'admin') {
111
+ return NextResponse.redirect(new URL('/403', req.url));
112
+ }
113
+
114
+ return response;
115
+ },
116
+ });
117
+
118
+ export default authProxy;
119
+ ```
120
+
121
+ ### 2. Create the API Client
122
+
123
+ ```ts
124
+ // lib/api.ts - Configure ONCE, use everywhere
125
+ import { createApiClient } from 'next-api-layer';
126
+
127
+ export const api = createApiClient({
128
+ sanitization: {
129
+ enabled: true,
130
+ // Skip specific fields (e.g., fields containing intentional HTML)
131
+ skipFields: ['html_content', 'raw_markdown'],
132
+ // Skip entire endpoints (glob patterns supported)
133
+ skipEndpoints: ['cms/*', 'pages/raw/**', 'content/html'],
134
+ },
135
+ i18n: {
136
+ enabled: true,
137
+ paramName: 'lang',
138
+ },
139
+ methodSpoofing: true, // For Laravel PUT/PATCH support
140
+ });
141
+ ```
142
+
143
+ ```ts
144
+ // Usage - ONE LINE anywhere in your app!
145
+ import { api } from '@/lib/api';
146
+
147
+ // Simple GET
148
+ const { data, success } = await api.get('users/profile');
149
+
150
+ // POST with body
151
+ const result = await api.post('projects', { body: { name: 'New Project' } });
152
+
153
+ // Per-request skip (overrides global config)
154
+ const rawHtml = await api.get('editor/content', { skipSanitize: true });
155
+
156
+ // With query params
157
+ const users = await api.get('users', { params: { page: 1, limit: 20 } });
158
+ ```
159
+
160
+ ### 3. Setup Auth Provider (Client-Side)
161
+
162
+ ```tsx
163
+ // app/providers.tsx
164
+ 'use client';
165
+
166
+ import { AuthProvider } from 'next-api-layer/client';
167
+
168
+ export function Providers({ children }: { children: React.ReactNode }) {
169
+ return (
170
+ <AuthProvider
171
+ userEndpoint="/api/auth/me"
172
+ loginEndpoint="/api/auth/login"
173
+ logoutEndpoint="/api/auth/logout"
174
+ swrConfig={{ revalidateOnFocus: true }}
175
+ >
176
+ {children}
177
+ </AuthProvider>
178
+ );
179
+ }
180
+ ```
181
+
182
+ ### 4. Use the Auth Hook
183
+
184
+ ```tsx
185
+ // components/UserProfile.tsx
186
+ 'use client';
187
+
188
+ import { useAuth } from 'next-api-layer/client';
189
+
190
+ export function UserProfile() {
191
+ const { user, isLoading, isAuthenticated, logout } = useAuth();
192
+
193
+ if (isLoading) return <div>Loading...</div>;
194
+ if (!isAuthenticated) return <div>Please login</div>;
195
+
196
+ return (
197
+ <div>
198
+ <h1>Welcome, {user.name}</h1>
199
+ <p>{user.email}</p>
200
+ <button onClick={logout}>Logout</button>
201
+ </div>
202
+ );
203
+ }
204
+ ```
205
+
206
+ ### 5. Server Components
207
+
208
+ ```tsx
209
+ // app/dashboard/page.tsx
210
+ import { getServerUser } from 'next-api-layer/server';
211
+ import { redirect } from 'next/navigation';
212
+
213
+ export default async function DashboardPage() {
214
+ const { user, isAuthenticated } = await getServerUser({
215
+ userCookie: 'userAuthToken',
216
+ apiBaseUrl: process.env.API_BASE_URL,
217
+ });
218
+
219
+ if (!isAuthenticated) {
220
+ redirect('/login');
221
+ }
222
+
223
+ return <div>Welcome, {user.name}!</div>;
224
+ }
225
+ ```
226
+
227
+ ## Architecture
228
+
229
+ ```
230
+ Developer writes: return api.get('client/projects/list');
231
+ Behind the scenes: 8-stage secure pipeline
232
+ ```
233
+
234
+ ### The Pipeline
235
+
236
+ 1. **Token Cascade** - Check user token → guest token → create guest
237
+ 2. **Token Validation** - Validate with backend, handle expiry
238
+ 3. **Token Refresh** - Auto-refresh expired tokens
239
+ 4. **Request Deduplication** - Prevent concurrent validation calls
240
+ 5. **XSS Sanitization** - Clean all response data
241
+ 6. **i18n Injection** - Add language parameter
242
+ 7. **Method Spoofing** - Laravel PUT/PATCH support
243
+ 8. **Error Handling** - Consistent error format
244
+
245
+ ## Security
246
+
247
+ `createAuthProxy` includes built-in security controls for production deployments.
248
+
249
+ ### CSRF Protection
250
+
251
+ Protects against Cross-Site Request Forgery using Fetch Metadata (modern browsers) and/or Signed Double-Submit Cookie pattern.
252
+
253
+ ```ts
254
+ const authProxy = createAuthProxy({
255
+ apiBaseUrl: process.env.API_BASE_URL!,
256
+ cookies: { user: 'userAuthToken', guest: 'guestAuthToken' },
257
+
258
+ csrf: {
259
+ enabled: true,
260
+ strategy: 'both', // 'fetch-metadata' | 'double-submit' | 'both'
261
+ cookieName: '__csrf', // Cookie name for token
262
+ headerName: 'x-csrf-token', // Header name for token
263
+ ignoreMethods: ['GET', 'HEAD', 'OPTIONS'],
264
+ trustSameSite: false, // Trust same-site requests
265
+ },
266
+ });
267
+ ```
268
+
269
+ **Behavior:**
270
+ - Safe methods (`GET`, `HEAD`, `OPTIONS`) are automatically skipped
271
+ - Unsafe methods are validated using Fetch Metadata headers and/or double-submit cookie
272
+ - Failed validation returns `403 Forbidden` and emits `csrf:fail` audit event
273
+
274
+ ### Rate Limiting
275
+
276
+ Token bucket algorithm for preventing abuse. In-memory store for single-instance deployments.
277
+
278
+ ```ts
279
+ const authProxy = createAuthProxy({
280
+ // ...base config
281
+
282
+ rateLimit: {
283
+ enabled: true,
284
+ windowMs: 60_000, // 1 minute window
285
+ maxRequests: 100, // Max requests per window
286
+ skipRoutes: ['/health', '/public/*'],
287
+ keyFn: (req) => req.headers.get('x-forwarded-for') || 'unknown',
288
+ onRateLimited: (req) => NextResponse.json(
289
+ { error: 'Too many requests' },
290
+ { status: 429 }
291
+ ),
292
+ },
293
+ });
294
+ ```
295
+
296
+ **Response Headers:**
297
+ - `X-RateLimit-Limit`: Max requests allowed
298
+ - `X-RateLimit-Remaining`: Requests remaining in window
299
+ - `X-RateLimit-Reset`: Unix timestamp when window resets
300
+ - `Retry-After`: Seconds until retry (on 429)
301
+
302
+ ### Audit Logging
303
+
304
+ Event-based security logging for monitoring and compliance.
305
+
306
+ ```ts
307
+ const authProxy = createAuthProxy({
308
+ // ...base config
309
+
310
+ audit: {
311
+ enabled: true,
312
+ events: [
313
+ 'auth:success',
314
+ 'auth:fail',
315
+ 'auth:refresh',
316
+ 'auth:guest',
317
+ 'access:denied',
318
+ 'csrf:fail',
319
+ 'rateLimit:exceeded',
320
+ 'error',
321
+ ],
322
+ logger: async (event) => {
323
+ // Send to your SIEM, logging service, or database
324
+ console.log('[AUDIT]', event.type, event.path, event.ip, event.success);
325
+
326
+ // Example: Send to external service
327
+ // await fetch('https://logs.example.com/audit', {
328
+ // method: 'POST',
329
+ // body: JSON.stringify(event),
330
+ // });
331
+ },
332
+ },
333
+ });
334
+ ```
335
+
336
+ **Event Structure:**
337
+ ```ts
338
+ interface AuditEvent {
339
+ type: AuditEventType; // Event type
340
+ timestamp: Date; // When it occurred
341
+ ip: string | null; // Client IP
342
+ userId?: string; // User ID if authenticated
343
+ path: string; // Request path
344
+ method: string; // HTTP method
345
+ success: boolean; // Whether action succeeded
346
+ metadata?: Record<string, unknown>; // Additional context
347
+ }
348
+ ```
349
+
350
+ ### Full Security Example
351
+
352
+ ```ts
353
+ import { createAuthProxy } from 'next-api-layer';
354
+
355
+ export default createAuthProxy({
356
+ apiBaseUrl: process.env.API_BASE_URL!,
357
+ cookies: { user: 'userAuthToken', guest: 'guestAuthToken' },
358
+
359
+ csrf: {
360
+ enabled: true,
361
+ strategy: 'both',
362
+ },
363
+
364
+ rateLimit: {
365
+ enabled: true,
366
+ windowMs: 60_000,
367
+ maxRequests: 100,
368
+ },
369
+
370
+ audit: {
371
+ enabled: true,
372
+ events: ['auth:success', 'auth:fail', 'csrf:fail', 'rateLimit:exceeded'],
373
+ logger: async (event) => {
374
+ console.log('[AUDIT]', JSON.stringify(event));
375
+ },
376
+ },
377
+ });
378
+ ```
379
+
380
+ ---
381
+
382
+ ## API Reference
383
+
384
+ ### createAuthProxy(config)
385
+
386
+ Creates a Next.js middleware for authentication.
387
+
388
+ ```ts
389
+ interface AuthProxyConfig {
390
+ apiBaseUrl: string; // Backend API URL
391
+
392
+ cookies: {
393
+ user: string; // User token cookie name
394
+ guest: string; // Guest token cookie name
395
+ options?: CookieOptions; // httpOnly, secure, sameSite, etc.
396
+ };
397
+
398
+ endpoints?: {
399
+ validate?: string; // Default: 'auth/me'
400
+ refresh?: string; // Default: 'auth/refresh'
401
+ guest?: string; // Default: 'auth/guest'
402
+ };
403
+
404
+ guestToken?: {
405
+ enabled: boolean;
406
+ credentials?: {
407
+ username: string;
408
+ password: string;
409
+ };
410
+ };
411
+
412
+ access?: {
413
+ protectedRoutes?: string[]; // Routes requiring auth
414
+ authRoutes?: string[]; // Routes for non-auth users (login, register)
415
+ publicRoutes?: string[]; // Completely public routes
416
+ };
417
+
418
+ cache?: {
419
+ ttl?: number; // Default: 2000ms
420
+ maxSize?: number; // Default: 100 tokens
421
+ };
422
+
423
+ // ======== Composability Hooks ========
424
+
425
+ // Runs BEFORE auth validation. Return NextResponse to bypass auth.
426
+ beforeAuth?: (req: NextRequest) => NextResponse | null | Promise<NextResponse | null>;
427
+
428
+ // Runs AFTER auth validation. Modify response or add custom logic.
429
+ afterAuth?: (req: NextRequest, response: NextResponse, authResult: AuthResult) => NextResponse | Promise<NextResponse>;
430
+ }
431
+
432
+ // AuthResult passed to afterAuth hook
433
+ interface AuthResult {
434
+ isAuthenticated: boolean; // true if valid user token
435
+ isGuest: boolean; // true if guest token
436
+ tokenType: string | null; // 'user', 'guest', etc.
437
+ user: Record<string, unknown> | null; // User data from token validation
438
+ }
439
+ ```
440
+
441
+ ### Different Backend Formats
442
+
443
+ Backend'iniz farklı response formatı dönüyorsa `responseMappers` ile uyumlu hale getirebilirsiniz:
444
+
445
+ ```ts
446
+ // Laravel (default format - no mapping needed)
447
+ // { success: true, data: { type: 'user', exp: 123, user: {...} } }
448
+
449
+ // Django REST Framework
450
+ const authProxy = createAuthProxy({
451
+ apiBaseUrl: process.env.API_BASE_URL!,
452
+ cookies: { user: 'token', guest: 'guest_token' },
453
+
454
+ responseMappers: {
455
+ // Django: { user: {...}, token_type: 'Bearer', exp: 123 }
456
+ parseAuthMe: (res: any) => {
457
+ if (!res?.user) return null;
458
+ return {
459
+ isValid: true,
460
+ tokenType: res.is_guest ? 'guest' : 'user',
461
+ exp: res.exp || null,
462
+ userData: res.user,
463
+ };
464
+ },
465
+
466
+ // Django: { access: 'new_token' }
467
+ parseRefreshToken: (res: any) => res?.access || null,
468
+
469
+ // Django: { token: 'guest_token' }
470
+ parseGuestToken: (res: any) => res?.token || null,
471
+ },
472
+ });
473
+
474
+ // .NET / ASP.NET Core
475
+ const authProxy = createAuthProxy({
476
+ apiBaseUrl: process.env.API_BASE_URL!,
477
+ cookies: { user: 'AuthToken', guest: 'GuestToken' },
478
+
479
+ responseMappers: {
480
+ // .NET: { isSuccess: true, result: { userId, email, role } }
481
+ parseAuthMe: (res: any) => {
482
+ if (!res?.isSuccess) return null;
483
+ return {
484
+ isValid: true,
485
+ tokenType: res.result?.role === 'Guest' ? 'guest' : 'user',
486
+ exp: res.result?.expiresAt,
487
+ userData: res.result,
488
+ };
489
+ },
490
+
491
+ // .NET: { token: 'xxx', expiresIn: 3600 }
492
+ parseRefreshToken: (res: any) => res?.token || null,
493
+ parseGuestToken: (res: any) => res?.token || null,
494
+ },
495
+ });
496
+
497
+ // Express.js / Custom Format
498
+ const authProxy = createAuthProxy({
499
+ apiBaseUrl: process.env.API_BASE_URL!,
500
+ cookies: { user: 'jwt', guest: 'guest_jwt' },
501
+
502
+ responseMappers: {
503
+ // Custom: { ok: true, payload: { sub, name, email } }
504
+ parseAuthMe: (res: any) => {
505
+ if (!res?.ok) return null;
506
+ return {
507
+ isValid: true,
508
+ tokenType: res.payload?.isGuest ? 'guest' : 'user',
509
+ exp: res.payload?.exp,
510
+ userData: res.payload,
511
+ };
512
+ },
513
+
514
+ parseRefreshToken: (res: any) => res?.newToken || res?.accessToken || null,
515
+ parseGuestToken: (res: any) => res?.guestToken || res?.token || null,
516
+ },
517
+ });
518
+ ```
519
+
520
+ ### createApiClient(config)
521
+
522
+ Creates an API client for making requests.
523
+
524
+ ```ts
525
+ interface ApiClientConfig {
526
+ sanitization?: {
527
+ enabled?: boolean; // Default: true
528
+ allowedTags?: string[]; // HTML tags to allow
529
+ skipFields?: string[]; // Fields to skip sanitization
530
+ };
531
+
532
+ i18n?: {
533
+ enabled?: boolean; // Default: false
534
+ paramName?: string; // Default: 'lang'
535
+ locales?: string[]; // Valid locale codes
536
+ defaultLocale?: string; // Fallback locale
537
+ };
538
+
539
+ methodSpoofing?: boolean; // Default: false (for Laravel)
540
+ }
541
+ ```
542
+
543
+ ## i18n Integration
544
+
545
+ Automatic locale detection and injection for multilingual applications.
546
+
547
+ ### How It Works
548
+
549
+ 1. **Middleware** extracts locale from URL path (e.g., `/tr/blog` → `tr`)
550
+ 2. **Sets `x-locale` header** on the request for downstream handlers
551
+ 3. **API Client** reads the header and appends `?lang={locale}` to backend requests
552
+
553
+ ### Configuration
554
+
555
+ ```ts
556
+ // middleware.ts - Proxy config
557
+ createAuthProxy({
558
+ apiBaseUrl: process.env.API_BASE_URL!,
559
+ cookies: { user: 'userToken', guest: 'guestToken' },
560
+
561
+ i18n: {
562
+ enabled: true,
563
+ locales: ['en', 'tr', 'ar'], // Valid locales
564
+ defaultLocale: 'en', // Fallback when no locale in path
565
+ },
566
+ });
567
+
568
+ // lib/api.ts - API client config
569
+ export const api = createApiClient({
570
+ apiBaseUrl: process.env.API_BASE_URL!,
571
+ cookies: { user: 'userToken', guest: 'guestToken' },
572
+
573
+ i18n: {
574
+ enabled: true,
575
+ paramName: 'lang', // Query param name (default: 'lang')
576
+ locales: ['en', 'tr', 'ar'],
577
+ defaultLocale: 'en',
578
+ },
579
+ });
580
+ ```
581
+
582
+ ### Request Flow
583
+
584
+ ```
585
+ User visits: /tr/blog
586
+
587
+ Middleware: Extracts 'tr' → Sets x-locale: tr header
588
+
589
+ Route Handler: await api.get('posts')
590
+
591
+ API Client: Reads x-locale header → Appends ?lang=tr
592
+
593
+ Backend: GET /api/posts?lang=tr
594
+ ```
595
+
596
+ ### With next-intl
597
+
598
+ When using with next-intl, use `afterAuth` hook to combine both middlewares:
599
+
600
+ ```ts
601
+ import { createAuthProxy } from 'next-api-layer';
602
+ import createIntlMiddleware from 'next-intl/middleware';
603
+ import { routing } from './i18n/routing';
604
+
605
+ const intlMiddleware = createIntlMiddleware(routing);
606
+
607
+ export default createAuthProxy({
608
+ apiBaseUrl: process.env.API_URL!,
609
+ cookies: { user: 'userToken', guest: 'guestToken' },
610
+
611
+ i18n: {
612
+ enabled: true,
613
+ locales: ['en', 'tr', 'ar'],
614
+ defaultLocale: 'en',
615
+ },
616
+
617
+ afterAuth: async (req, response, _authResult) => {
618
+ if (req.nextUrl.pathname.startsWith('/api')) {
619
+ return response; // API routes keep auth response
620
+ }
621
+
622
+ // Page routes: run next-intl but preserve auth cookies
623
+ const intlResponse = intlMiddleware(req);
624
+ response.cookies.getAll().forEach(cookie => {
625
+ intlResponse.cookies.set(cookie.name, cookie.value, {
626
+ httpOnly: cookie.httpOnly,
627
+ secure: cookie.secure,
628
+ sameSite: cookie.sameSite,
629
+ path: cookie.path,
630
+ maxAge: cookie.maxAge,
631
+ });
632
+ });
633
+
634
+ return intlResponse;
635
+ },
636
+ });
637
+ ```
638
+
639
+ ### AuthProvider
640
+
641
+ React context provider for client-side auth state.
642
+
643
+ ```tsx
644
+ <AuthProvider
645
+ userEndpoint="/api/auth/me"
646
+ loginEndpoint="/api/auth/login"
647
+ logoutEndpoint="/api/auth/logout"
648
+ swrConfig={{ refreshInterval: 0 }}
649
+ onLogin={(user) => console.log('Logged in:', user)}
650
+ onLogout={() => console.log('Logged out')}
651
+ onError={(error) => console.error(error)}
652
+ >
653
+ {children}
654
+ </AuthProvider>
655
+ ```
656
+
657
+ ### useAuth()
658
+
659
+ Hook to access authentication state.
660
+
661
+ ```ts
662
+ const {
663
+ user, // UserData | null
664
+ isLoading, // boolean
665
+ isAuthenticated, // boolean (true for real users)
666
+ isGuest, // boolean (true for guest tokens)
667
+ error, // Error | null
668
+ login, // (credentials) => Promise<LoginResult>
669
+ logout, // () => Promise<void>
670
+ refresh, // () => Promise<void>
671
+ } = useAuth();
672
+ ```
673
+
674
+ ### getServerUser(options)
675
+
676
+ Get user data in Server Components.
677
+
678
+ ```ts
679
+ const { user, isAuthenticated, isGuest, token } = await getServerUser({
680
+ userCookie: 'userAuthToken',
681
+ guestCookie: 'guestAuthToken',
682
+ apiBaseUrl: process.env.API_BASE_URL,
683
+ });
684
+ ```
685
+
686
+ ## Public API / Skip Auth
687
+
688
+ Bazı endpoint'ler (haber siteleri, public içerikler) authentication gerektirmez. Bu durumlar için `skipAuth` özelliğini kullanabilirsiniz:
689
+
690
+ ### Global Config
691
+
692
+ ```ts
693
+ const api = createApiClient({
694
+ auth: {
695
+ // Bu pattern'lere uyan endpoint'ler token göndermez
696
+ publicEndpoints: ['news/*', 'categories', 'public/**'],
697
+
698
+ // Opsiyonel: Tüm endpoint'ler default olarak public olsun
699
+ // skipByDefault: true,
700
+ },
701
+ });
702
+ ```
703
+
704
+ ### Per-Request Override
705
+
706
+ ```ts
707
+ // Pattern'e uysa bile token gönder
708
+ await api.get('news/premium-article', { skipAuth: false });
709
+
710
+ // Pattern'e uymasa bile token gönderme
711
+ await api.get('some-endpoint', { skipAuth: true });
712
+ ```
713
+
714
+ ### Route Handler (createProxyHandler)
715
+
716
+ API route handler'ınızda `createProxyHandler` kullanarak backend'e proxy yapabilirsiniz:
717
+
718
+ ```ts
719
+ // app/api/[...path]/route.ts
720
+ import { createProxyHandler } from 'next-api-layer';
721
+
722
+ const handler = createProxyHandler({
723
+ apiBaseUrl: process.env.API_BASE_URL!,
724
+ publicEndpoints: ['news/*', 'categories', 'public/**'],
725
+ debug: process.env.NODE_ENV === 'development',
726
+ });
727
+
728
+ export const GET = handler;
729
+ export const POST = handler;
730
+ export const PUT = handler;
731
+ export const DELETE = handler;
732
+ ```
733
+
734
+ ## Peer Dependencies
735
+
736
+ - `next` >= 14.0.0
737
+ - `react` >= 18.0.0
738
+ - `swr` >= 2.0.0 (optional, for client module)
739
+ - `next-intl` >= 3.0.0 (optional, for i18n)
740
+
741
+ ## License
742
+
743
+ MIT