npm package: @ghersidl/api-helpers
Description: Framework-agnostic API utilities for building consistent and maintainable REST APIs
Author: ghersidl
Latest version: 1.0.0

# API Helpers

🚀 **Framework-agnostic API utilities for building consistent and maintainable REST APIs**

A lightweight, TypeScript-first library that provides standardized response helpers, validation utilities, and constants for building REST APIs. Works seamlessly with **Next.js**, **Express**, or any Node.js framework through adapters.

## ✨ Features

- 🎯 **Framework-agnostic** - Works with Next.js or custom implementations
- 📦 **Zero dependencies** (framework packages are peer dependencies)
- 🔒 **TypeScript-first** - Full type safety and IntelliSense support
- ✅ **Consistent responses** - Standardized API response format
- 🛡️ **Built-in validations** - Common validation helpers included
- 🔌 **Adapter pattern** - Easy to extend for any framework
- 📝 **Well documented** - Comprehensive JSDoc comments

## 📦 Installation

```bash
npm install @ghersidl/api-helpers
```

## 🚀 Quick Start

### With Next.js

```typescript
// app/api/setup.ts
import { initApiHelpers, NextJsAdapter } from '@ghersidl/api-helpers';

// Initialize once at app startup
initApiHelpers(new NextJsAdapter());
```

```typescript
// app/api/users/[id]/route.ts
import { NextRequest } from 'next/server';
import {
  successResponse,
  notFoundResponse,
  validateRequired,
  HTTP_STATUS
} from '@ghersidl/api-helpers';

export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;

  // Validate required parameter
  const error = validateRequired(id, 'id');
  if (error) return error;

  // Fetch user
  const user = await getUserById(id);
  if (!user) {
    return notFoundResponse('User not found');
  }

  return successResponse(user);
}
```

### With Generic Adapter (Custom Framework)

```typescript
// server.ts
import { initApiHelpers, GenericAdapter } from '@ghersidl/api-helpers';

initApiHelpers(new GenericAdapter());

// Your custom handler
async function handleRequest(req) {
  const user = await getUserById(req.params.id);

  if (!user) {
    const response = notFoundResponse('User not found');
    return { status: response.status, body: response.body };
  }

  const response = successResponse(user);
  return { status: response.status, body: response.body };
}
```

## 📚 API Reference

### Response Helpers

All response helpers return framework-specific responses with standardized formats:

#### Success Responses

```typescript
// 200 OK
successResponse({ id: '123', name: 'John' });
// Returns: { ok: true, data: { id: '123', name: 'John' } }

// 201 Created
createdResponse({ id: '456', name: 'New Item' });

// 204 No Content
noContentResponse();
```

#### Error Responses

```typescript
// 400 Bad Request
badRequestResponse('Invalid input', { field: 'email' });

// 401 Unauthorized
unauthorizedResponse();

// 403 Forbidden
forbiddenResponse('Access denied');

// 404 Not Found
notFoundResponse('Resource not found');

// 422 Validation Error
validationErrorResponse('Validation failed', {
  email: 'Invalid format',
  password: 'Too short'
});

// 500 Internal Server Error
internalErrorResponse();
```

### Validation Helpers

All validation helpers return `null` if valid, or an error response if invalid:

```typescript
// Validate required field
const error = validateRequired(id, 'id');
if (error) return error;

// Validate positive number
validatePositiveNumber(price, 'price', 1000); // max: 1000

// Validate enum
validateEnum(status, ['active', 'inactive'], 'status');

// Validate array
validateArray(items, 'items', 1); // minLength: 1

// Validate boolean
validateBoolean(enabled, 'enabled');

// Validate integer
validateInteger(page, 'page', 1, 100); // min: 1, max: 100

// Validate not empty object
validateNotEmpty(updateData);

// Validate JSON body
const result = validateJsonBody(bodyString);
if ('error' in result) return result.error;
const { data } = result;
```

### Constants

```typescript
import { HTTP_STATUS, API_ERROR_MESSAGES, PAGINATION } from '@ghersidl/api-helpers';

// HTTP Status Codes
HTTP_STATUS.OK                    // 200
HTTP_STATUS.CREATED              // 201
HTTP_STATUS.NO_CONTENT           // 204
HTTP_STATUS.BAD_REQUEST          // 400
HTTP_STATUS.UNAUTHORIZED         // 401
HTTP_STATUS.FORBIDDEN            // 403
HTTP_STATUS.NOT_FOUND            // 404
HTTP_STATUS.UNPROCESSABLE_ENTITY // 422
HTTP_STATUS.INTERNAL_SERVER_ERROR // 500

// Error Messages
API_ERROR_MESSAGES.UNAUTHORIZED
API_ERROR_MESSAGES.NOT_FOUND
API_ERROR_MESSAGES.VALIDATION_FAILED
// ... and many more

// Pagination
PAGINATION.DEFAULT_ITEMS_PER_PAGE // 10
PAGINATION.MAX_ITEMS_PER_PAGE     // 100
PAGINATION.DEFAULT_PAGE           // 1
```

### Authentication Integration

```typescript
// Set a custom auth validator
import { initApiHelpers, setAuthValidator, NextJsAdapter } from '@ghersidl/api-helpers';

initApiHelpers(
  new NextJsAdapter(),
  async (request) => {
    // Your authentication logic
    const user = await getAuthenticatedUser(request);
    return user; // { id: string, email?: string, ... }
  }
);

// Then use in your routes
import { validateAuth } from '@ghersidl/api-helpers';

export async function GET(request: NextRequest) {
  const user = await getAuthenticatedUser(request);
  const error = validateAuth(user);
  if (error) return error; // Returns 401 if not authenticated

  // User is authenticated, proceed...
}
```

## 🔌 Adapters

### Built-in Adapters

#### Next.js Adapter

```typescript
import { NextJsAdapter } from '@ghersidl/api-helpers/adapters/nextjs';
initApiHelpers(new NextJsAdapter());
```

#### Generic Adapter

Returns plain objects for maximum flexibility:

```typescript
import { GenericAdapter } from '@ghersidl/api-helpers/adapters/generic';
const api = initApiHelpers(new GenericAdapter());

const response = api.successResponse({ id: '123' });
// { status: 200, body: { ok: true, data: { id: '123' } } }
```

### Custom Adapter

Implement your own adapter for any framework:

```typescript
import type { ResponseAdapter } from '@ghersidl/api-helpers/types';

class MyFrameworkAdapter implements ResponseAdapter {
  json<T>(body: T, status: number, headers?: Record<string, string>) {
    // Return framework-specific response
    return myFramework.response(body, { status, headers });
  }

  empty(status: number, headers?: Record<string, string>) {
    return myFramework.emptyResponse({ status, headers });
  }
}

initApiHelpers(new MyFrameworkAdapter());
```

## 🎯 Response Format

All API responses follow this consistent format:

### Success Response
```typescript
{
  ok: true,
  data: T,
  message?: string
}
```

### Error Response
```typescript
{
  ok: false,
  error: string,
  message?: string,
  statusCode?: number
}
```

## 📋 Migration Guide

### From Next.js Direct Usage

**Before:**
```typescript
import { NextResponse } from 'next/server';

export async function GET() {
  if (!user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  return NextResponse.json({ ok: true, data: user });
}
```

**After:**
```typescript
import { successResponse, unauthorizedResponse } from '@ghersidl/api-helpers';

export async function GET() {
  if (!user) {
    return unauthorizedResponse();
  }
  return successResponse(user);
}
```

### From Express Direct Usage

**Before:**
```typescript
app.get('/users', (req, res) => {
  if (!user) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  res.json({ ok: true, data: user });
});
```

**After:**
```typescript
import { successResponse, unauthorizedResponse, GenericAdapter } from '@ghersidl/api-helpers';

initApiHelpers(new GenericAdapter());

app.get('/users', (req, res) => {
  if (!user) {
    const response = unauthorizedResponse();
    return res.status(response.status).json(response.body);
  }
  const response = successResponse(user);
  res.status(response.status).json(response.body);
});
```

## 🧪 Testing

```typescript
import { GenericAdapter, successResponse, HTTP_STATUS } from '@ghersidl/api-helpers';

// Use GenericAdapter for testing
initApiHelpers(new GenericAdapter());

describe('API Responses', () => {
  it('should return success response', () => {
    const response = successResponse({ id: '123' });

    expect(response.status).toBe(HTTP_STATUS.OK);
    expect(response.body).toEqual({
      ok: true,
      data: { id: '123' }
    });
  });
});
```

## 📝 TypeScript Support

Full TypeScript support with comprehensive types:

```typescript
import type {
  ApiResponse,
  ApiSuccessResponse,
  ApiErrorResponse,
  PaginatedResponse,
  ResponseAdapter,
  AuthenticatedUser
} from '@ghersidl/api-helpers';

// Type-safe API responses
async function fetchUser(id: string): Promise<ApiResponse<User>> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}
```

## 🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## 📄 License

MIT

## 🔗 Related

- [Next.js Documentation](https://nextjs.org/docs)
- [Express Documentation](https://expressjs.com/)
- [REST API Best Practices](https://restfulapi.net/)

---

Made with ❤️ for building better APIs