npm package: @sidev/api-handler
Description: A type-safe library for handling API responses and function returns in JS and TS applications.
Author: [si] Slavko Ivanovic
Latest version: 1.0.2

### @sidev/

# api-handler

### A type-safe library for handling API responses and function returns in JS and TS applications.

## Features

- Provides consistent return values and type safety.
- Standardizes success and error handling for better readability.
- Integrates easily with TypeScript and JavaScript projects.
- Supports custom data structures and application-specific types.

### TOC

<!-- TOC start (generated with https://github.com/derlin/bitdowntoc) -->

- [Installation](#installation)
- [Usage](#usage)
  - [1. Basic Setup](#1-basic-setup)
  - [2. Handling Errors](#2-handling-errors)
    - [Note:](#note)
  - [3. Handling Success Responses](#3-handling-success-responses)
  - [4. Using in Functions](#4-using-in-functions)
  - [5. Example with Custom Types (TS)](#5-example-with-custom-types-ts)
  - [6. apiSuccess() Important notes and .warning key](#6-apisuccess-important-notes-and-warning-key)
- [Types Overview](#types-overview)
  - [`ApiHandler`](#apihandler)
  - [`ValidData`](#validdata)
  - [`ApiResponseBase`](#apiresponsebase)
  - [`ApiSuccessResponse`](#apisuccessresponse)
  - [`ApiErrorResponse`](#apierrorresponse)
  - [`ApiResponse`](#apiresponse)
  - [`ApiResponseWith`](#apiresponsewith)
  - [`AppError`](#apperror)
  - [`ApiErrorsMap`](#apierrorsmap)
- [API Reference](#api-reference)
  - [`createApiHandler(apiErrors: ApiErrorsMap)`](#createapihandlerapierrors-apierrorsmap)
  - [`apiError(error: unknown): ApiErrorResponse`](#apierrorerror-unknown-apierrorresponse)
  - [`apiSuccess<T extends ValidData>(params: { data: T; message?: string }): ApiSuccessResponse<T>`](#apisuccesst-extends-validdataparams--data-t-message-string--apisuccessresponset)
- [License](#license)

<!-- TOC end -->

## Installation

You can install `@sidev/api-handler` using npm or yarn:

```bash
npm install @sidev/api-handler
```

or

```bash
yarn add @sidev/api-handler
```

<!--
## Features

- Standardized API response structure for success and error handling.
- Easy-to-use methods for generating API success and error responses. -->

---

## Usage

### 1. Basic Setup

Import the library and create your API handler:

```typescript
import { createApiHandler } from '@sidev/api-handler'

const APP_ERRORS = {
  AUTH_MEMBER_EXISTS: {
    code: 'AUTH_MEMBER_EXISTS',
    message: 'Member already exists.',
    uiMessages: ['This member already exists.', 'Please log in instead.'],
  },
  AUTH_NO_CREDENTIALS: {
    code: 'AUTH_NO_CREDENTIALS',
    message: 'No credentials provided.',
    uiMessages: ['No credentials were provided.', 'Please try again.'],
  },
}

const { apiError, apiSuccess, ERROR, CODE } = createApiHandler(APP_ERRORS)
```

### 2. Handling Errors

You can generate error responses using `apiError()`:

```typescript
const errorInstance = new Error(CODE.AUTH_MEMBER_EXISTS)
const errorResponse1 = apiError(errorInstance)

console.log('apiError(errorInstance) returns:', errorResponse1)

// Using error code as a string
const errorCode = CODE.AUTH_MEMBER_EXISTS
const errorResponse2 = apiError(errorCode)

console.log('apiError(errorCode) returns:', errorResponse2)
```

#### Note:

```typescript
/*
  When using `apiError` to throw error codes, you can either pass the error code string directly or wrap it in a new `Error` object.
  While both approaches work, wrapping the error code in a new `Error` object will provide a more accurate stack trace, showing the exact location where the error was thrown. This can be a minor but useful difference for debugging purposes.
 */
try {
  // Example of throwing an error with a more accurate stack trace
  throw new Error(CODE.AUTH_MEMBER_EXISTS)

  // Example of throwing an string with error code
  throw CODE.AUTH_MEMBER_EXISTS
  //
} catch (error) {
  // return from catch block
  return apiError(error)
}
```

### 3. Handling Success Responses

To generate success responses, use `apiSuccess()`:

```typescript
const dataObj = {
  data: { key: 'value' },
  message: 'Operation successful',
}
const successResponse = apiSuccess(dataObj)
console.log(successResponse)
```

### 4. Using in Functions

Here’s an example of using `apiSuccess()` and `apiError()` in a function:

```typescript
import { createApiHandler } from '@sidev/api-handler'
import { myErrors } from './my-errors'

const { apiError, apiSuccess, CODE } = createApiHandler(myErrors)

const handleRequest = (condition: string) => {
  try {
    if (condition === 'error') {
      throw new Error(CODE.NOT_AUTHORIZED)
      // or
      // throw CODE.AUTH_NO_CREDENTIALS;
    }
    // go on
    return apiSuccess({
      data: { abc: 'xyz' },
      message: 'Request handled successfully',
    })
  } catch (error) {
    // return what was thrown with apiError()
    // if any other error happen it will be handled also uniformly
    // and return expected ApiErrorResponse object
    return apiError(error)
  }
}

console.log(handleRequest('error'))
/* logs:
{
  success: false,
  error: Error: Not authorized
      at handleRequest ... ...
  message: 'Not authorized',
  code: 'NOT_AUTHORIZED',
  uiMessages: [
    'You are Not authorized',
    'This content is only available to authorized users.',
    'Look for the login button in the top right corner.'
  ]
}
*/
console.log(handleRequest('success'))
/* logs:
{
  success: true,
  data: { abc: 'xyz' },
  message: 'Request handled successfully'
}
*/
```

### 5. Example with Custom Types (TS)

You can separate your error definitions in a centralized file, even for the whole app. Use type ApiErrorsMap

```typescript
// my-errors.ts

import { type ApiErrorsMap } from '@sidev/api-handler'

// Centralized error definitions
export const myErrors: ApiErrorsMap = {
  NO_ACCOUNT: {
    code: 'NO_ACCOUNT',
    message: 'Account not found',
    uiMessages: [
      'Try creating an account.',
      'If you are an admin, please contact developer team.',
    ],
  },
  NOT_AUTHORIZED: {
    code: 'NOT_AUTHORIZED',
    message: 'Not authorized',
    uiMessages: [
      'You are Not authorized',
      'This content is only available to authorized users.',
      'Look for the login button in the top right corner.',
    ],
  },
  // ... more
}

// only for this example mock function
export async function simulatedDb(id: number) {
  await new Promise((resolve) => setTimeout(resolve, 30))
  /* --- 555 id will simulate unpredicted error from db --- */
  switch (id) {
    case 555:
      throw new Error('Some error outside your definitions')
    case 10:
      return { id, name: 'John Doe', role: 'Admin' }
    case 20:
      return { id, name: 'Smith Bob', role: 'Customer' }
    default:
      return {}
  }
}
```

Now, in module, you can define custom types for your .data with type ApiResponseWith

```typescript
// example.ts

import { createApiHandler, type ApiResponseWith } from '@sidev/api-handler'
import { myErrors, simulatedDb } from './my-errors'

const { apiError, apiSuccess, CODE } = createApiHandler(myErrors)

type UserAccount = {
  id: number
  name: string
  role: string
}

const getAdminAccount = async (
  id: number
): Promise<ApiResponseWith<UserAccount>> => {
  try {
    const userAccount = await simulatedDb(id) // a mock function
    if (userAccount.id) {
      if (userAccount.role !== 'Admin') {
        throw new Error(CODE.NOT_AUTHORIZED)
      }
      return apiSuccess({
        /* --- data property is typechecking for UserAccount --- */
        data: userAccount,
        message: 'Account fetched successfully', // optional default "Success"
      })
    }
    throw new Error(CODE.NO_ACCOUNT)
  } catch (error) {
    return apiError(error)
  }
}

/* --- call it with different id param to test outputs --- */
let response
response = await getAdminAccount(20) // success:false NOT_AUTHORIZED
response = await getAdminAccount(10) // success:true
response = await getAdminAccount(555) // success:false UNEXPECTED ERROR
response = await getAdminAccount(1000) // success:false NO_ACCOUNT

if (response.success) {
  console.log('success response', response)
} else {
  console.log('error response', response)
  /* --- do something with uiMessages on frontend --- */
  // showToast(response.uiMessages[0])
  response.uiMessages.map((message: string) => console.log(message))
}
```

### 6. apiSuccess() Important notes and .warning key

Function always returns ApiSuccessResponse object. On bad data attaches warning

```typescript
import { createApiHandler } from '@sidev/api-handler'

const { apiError, apiSuccess } = createApiHandler({})

/**
 * JS runtime check in: apiSucess({ data : ValidData })
 *
 * - Call to apiSuccess() always! returns success response.
 * - In case of misuse of apiSuccess params response.warning is attached.
 * - Bad params or value, is converted to String and returned in response.data.
 *
 * _________________________________________________________________
 */

console.log(apiSuccess()) // with {warning}
console.log(apiSuccess(null)) // with {warning}
/* logs:
{
  success: true,
  data: 'null',
  message: 'Success',
  warning: 'Bad params in apiSuccess(params). Params should be { data: value } object. Got null.'
}
*/
console.log(apiSuccess({ abc: 1 })) // with {warning}
console.log(apiSuccess(100)) // with {warning}

console.log(apiSuccess({ data: null, message: 'lorem ipsum' })) // with {warning}
console.log(apiSuccess({ data: undefined })) // with {warning}
console.log(apiSuccess({ data: 888 })) // with {warning}

console.log(apiSuccess({ data: 'lorem ipsum' })) // OK
console.log(apiSuccess({ data: [4, 5, 6] })) // OK
console.log(apiSuccess({ data: { abc: 2 } })) // OK

// ---- In apiError call with bad params will return with code: UNKNOWN_ERROR

console.log(apiError())
/* logs:
{
  success: false,
  error: Error: Unknown error occurred
  message: 'Unknown error occurred',
  code: 'UNKNOWN_ERROR',
  uiMessages: [ 'An unknown error occurred.' ]
}
*/
```

## Types Overview

### `ApiHandler`

Represents a unified API response interface that can be either success or error response.

```typescript
declare function createApiHandler(apiErrors: ApiErrorsMap): ApiHandler

export type ApiHandler = {
  apiError: (error: unknown) => ApiErrorResponse
  apiSuccess: <T extends ValidData>(params: {
    data: T
    message?: string
  }) => ApiSuccessResponse<T>
  readonly ERROR: ApiErrorsMap
  readonly CODE: Record<string, string>
}
```

### `ValidData`

Represents valid types of data that can be used in success responses. It can be an object, array, or string.

```typescript
type ValidData = object | any[] | string
```

### `ApiResponseBase`

The base structure for all API responses, containing a `success` flag and an optional `message`.

```typescript
export type ApiResponseBase = {
  success: boolean
  message?: string
}
```

### `ApiSuccessResponse`

Represents a successful API response. It extends `ApiResponseBase` and includes the `data` field containing the response data. Other error-specific fields are set to `never`.

```typescript
export type ApiSuccessResponse<T extends ValidData = object> =
  ApiResponseBase & {
    success: true
    data: T
    warning?: string // internal for misuse of apiSuccess
    error?: never
    code?: never
    uiMessages?: never
  }
```

### `ApiErrorResponse`

Represents an error response. It extends `ApiResponseBase` and includes additional fields for `error`, `code`, and `uiMessages`.

```typescript
export type ApiErrorResponse = ApiResponseBase & {
  success: false
  error: Error
  code: string
  uiMessages: string[]
  data?: never
  warning?: never
}
```

### `ApiResponse`

A union type that can be either a success or an error response.

```typescript
export type ApiResponse = ApiSuccessResponse | ApiErrorResponse
```

### `ApiResponseWith`

A generic type that extends `ApiResponse` to include specific data types in success responses.

```typescript
export type ApiResponseWith<T extends ValidData> =
  | ApiSuccessResponse<T>
  | ApiErrorResponse
```

### `AppError`

Defines the structure for application-specific errors, including `code`, `message`, and `uiMessages`.

```typescript
export type AppError = {
  code: string
  message: string
  uiMessages: string[]
}
```

### `ApiErrorsMap`

A record type that maps error codes to `AppError` objects.

```typescript
export type ApiErrorsMap = Record<string, AppError>
```

## API Reference

### `createApiHandler(apiErrors: ApiErrorsMap)`

- **Description**: Initializes an API handler with custom error configurations.
- **Parameters**:
  - `apiErrors`: A map of application-specific error codes to error details, including messages and UI messages.
- **Returns**: An API handler object containing methods for generating standardized success and error responses.

### `apiError(error: unknown): ApiErrorResponse`

- **Description**: Processes an error and returns a structured API error response.
- **Parameters**:
  - `error`: The error instance or code to be transformed into an API error response.
- **Returns**: An `ApiErrorResponse` object containing error details and UI messages.

### `apiSuccess<T extends ValidData>(params: { data: T; message?: string }): ApiSuccessResponse<T>`

- **Description**: Generates a standardized API success response.
- **Parameters**:
  - `params`: An object containing:
    - `data`: The data to include in the success response.
    - `message`: An optional message to accompany the success response.
- **Returns**: An `ApiSuccessResponse<T>` object with the provided data and message.

## About

### Author

**[si] Slavko Ivanovic**

- [github/slavkoivanovic](https://github.com/slavkoivanovic)

### License

Released under the [MIT License](LICENSE).