https://github.com/bankole2000/service-reponse-formatter
Yet another attempt at standardising JSON http responses across microservices
https://github.com/bankole2000/service-reponse-formatter
api functions http json response response-management server standard utility utils
Last synced: 2 months ago
JSON representation
Yet another attempt at standardising JSON http responses across microservices
- Host: GitHub
- URL: https://github.com/bankole2000/service-reponse-formatter
- Owner: Bankole2000
- Created: 2024-04-26T16:19:56.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-05-09T02:28:14.000Z (about 2 years ago)
- Last Synced: 2026-04-03T21:40:01.874Z (3 months ago)
- Topics: api, functions, http, json, response, response-management, server, standard, utility, utils
- Language: JavaScript
- Homepage: https://www.npmjs.com/package/@neoncoder/service-response
- Size: 731 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
Awesome Lists containing this project
README
## Service Response Formatter
A Collection of utility functions to quickly scaffold and standardize server-side function returns and http responses with nodejs.
Service Response - YAHTTPRF
Yet Another HTTP Response Formatter
Table of Contents
## Installation
Meant to be installed on a __nodejs express__ server with either JavaScript or Typescript
```sh
npm install @neoncoder/service-response
```
The package exposes types, interfaces, and functions to help standardize and easily scaffold json http responses and function returns
`ServiceResponse` - interface for http responses
```ts
type ServiceResponse = {
// data sent to the client (including pagination etc)
data?: any,
// error summary (e.g. error.message)
errMessage?: string | undefined | null,
// error details (raw error object / instance)
error?: unknown,
// suggested fixes to help client / end user (e.g. contact support)
fix?: string | undefined | null,
// short message describing operation result (e.g. 'Login successful')
message: string,
// any req, res metadata (e.g. requestId, appId, version, timestamps etc)
meta?: any,
// if using accesstoken and token is refreshed, you can send the new token here
newAccessToken?: string | undefined | null,
// was the operation successful or not?
success: boolean,
// server response status code - i.e. 200, 201, 404, etc
statusCode: number,
}
```
`TStatus` - Interface for relaying Data Access / CRUD operation results (or function output in general)
```ts
type TStatus = {
// operation status code - i.e. 200, 201, 404, etc
code: number,
// short message describing result (e.g. 'Created successfully')
message: string,
// suggested fixes (optional e.g. contact support)
fix?: string,
// resultant data from operation (including pagination etc)
data?: any,
// error details (raw error object / instance)
error?: any,
// "OK" | "Created" | "BadRequest" | "Unauthorized" etc
statusType: string
}
```
## Usage
### Generating `ServiceResponse`
- Import and invoke the generator function (e.g. `OK`, `NotFound`, `Created`, `Unauthorized` etc) passing in none or any of the `ServiceResponse` type options (i.e. `{message, success, data, fix, meta, error, errMessage, newAccessToken}`);
```ts
import express, { Request, Response } from 'express';
import { OK, NotFound, Unauthorized, ServiceResponse } from '@neoncoder/service-response'
const app = express();
// generate OK - 200 response
app.get('/endpoint', async (req: Request, res: Response) => {
const someData = await getSomeData()
const sr: ServiceReponse = OK({data: someData})
res.status(sr.statusCode).send(sr)
})
// generate NotFound - 404 response
app.get('/not-found', async (req: Request, res: Response) => {
const sr: ServiceReponse = NotFound({message: 'This route does not exist'})
res.status(sr.statusCode).send(sr)
})
// generate Unauthorized - 401 response
app.get('/unauthorized', async (req: Request, res: Response) => {
if(!checkUserAuth()){
const sr: ServiceReponse = Unauthorized({})
return res.status(sr.statusCode).send(sr)
}
next()
})
```
- Or Use the `Rez` object and call the functions from there
```ts
import express, { Request, Response } from 'express';
import { Rez, ServiceResponse } from '@neoncoder/service-response'
const app = express();
// generate OK - 200 response
app.get('/endpoint', async (req: Request, res: Response) => {
const someData = await getSomeData()
const sr: ServiceReponse = Rez.OK({data: someData})
res.status(sr.statusCode).send(sr)
})
// generate NotFound - 404 response
app.get('/not-found', async (req: Request, res: Response) => {
const sr: ServiceReponse = Rez.NotFound({fix: 'check the route exists'})
res.status(sr.statusCode).send(sr)
})
```
### Generating `TStatus`
- The `statusMap.get()` method returns a function that takes in an options object as parameter (i.e.`{message, fix, error, data}`) to return a `TStatus` object;
```ts
import express, { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client'
import { statusMap, TStatus, Rez } from '@neoncoder/service-response'
const prisma = new PrismaClient()
const app = express();
app.get('/user/:id', async (req: Request, res: Response) => {
let result: TStatus;
try {
const user = await prisma.user.findUnique({
where: {
id: req.params.id
}
})
// if user is found return 200 with user data, else return 404
result = user
? statusMap.get(200)!({data: user})
: statusMap.get(404)!({})
} catch (error: any) {
result = statusMap.get(500)!({error})
}
// Quickly generate a ServiceResponse from the TStatus object / result
const sr: ServiceResponse = Rez[result.statusType]({...result})
return res.status(sr.statusCode).send(sr)
})
```
- The `statuses` object contains a predefined list of TStatus Objects that can be referenced by the Status Type. Using the previous example with the `statuses` object becomes:
```ts
import express, { Request, Response } from 'express';
import { PrismaClient } from '@prisma/client'
import { statuses, TStatus, Rez } from '@neoncoder/service-response'
const prisma = new PrismaClient()
const app = express();
app.get('/user/:id', async (req: Request, res: Response) => {
let result: TStatus;
try {
const user = await prisma.user.findUnique({
where: {
id: req.params.id
}
})
// if user is found return 200 with user data, else return 404
result = user
? {...statuses.OK, data: user}
: {...statuses.NotFound}
} catch (error: any) {
result = {...statuses.InternalServerError, error}
}
// Quickly generate a ServiceResponse from the TStatus object / result
const sr: ServiceResponse = Rez[result.statusType]({...result})
return res.status(sr.statusCode).send(sr)
})
```
## List of Status Codes, Types & Generator Fxns
| Status Code | StatusType | Generate `ServiceResponse` | Generate `TStatus` |
| ----------- | ---------------------- | -------------------------------------------------------------- | ------------------------- |
| 200 | `OK` | `OK({})` \|\| `Rez.OK({})` | `statusMap.get(200)!({})` |
| 201 | `Created` | `Created({})` \|\| `Rez.Created({})` | `statusMap.get(201)!({})` |
| 204 | `NoContent` | `NoContent({})` \|\| `Rez.NoContent({})` | `statusMap.get(204)!({})` |
| 400 | `BadRequest` | `BadRequest({})` \|\| `Rez.BadRequest({})` | `statusMap.get(400)!({})` |
| 401 | `Unauthorized` | `Unauthorized({})` \|\| `Rez.Unauthorized({})` | `statusMap.get(401)!({})` |
| 403 | `Forbidden` | `Forbidden({})` \|\| `Rez.Forbidden({})` | `statusMap.get(403)!({})` |
| 404 | `NotFound` | `NotFound({})` \|\| `Rez.NotFound({})` | `statusMap.get(404)!({})` |
| 405 | `MethodNotAllowed` | `MethodNotAllowed({})` \|\| `Rez.MethodNotAllowed({})` | `statusMap.get(405)!({})` |
| 408 | `TimeoutError` | `TimeoutError({})` \|\| `Rez.TimeoutError({})` | `statusMap.get(408)!({})` |
| 415 | `UnsupportedMediaType` | `UnsupportedMediaType({})` \|\| `Rez.UnsupportedMediaType({})` | `statusMap.get(415)!({})` |
| 417 | `ExpectationFailed` | `ExpectationFailed({})` \|\| `Rez.ExpectationFailed({})` | `statusMap.get(417)!({})` |
| 422 | `UnprocessableEntity` | `UnprocessableEntity({})` \|\| `Rez.UnprocessableEntity({})` | `statusMap.get(422)!({})` |
| 429 | `TooManyRequests` | `TooManyRequests({})` \|\| `Rez.TooManyRequests({})` | `statusMap.get(429)!({})` |
| 500 | `InternalServerError` | `InternalServerError({})` \|\| `Rez.InternalServerError({})` | `statusMap.get(500)!({})` |
| 503 | `ServiceUnavailable` | `ServiceUnavailable({})` \|\| `Rez.ServiceUnavailable({})` | `statusMap.get(503)!({})` |
| 504 | `GatewayTimeout` | `GatewayTimeout({})` \|\| `Rez.GatewayTimeout({})` | `statusMap.get(504)!({})` |
## Other Utilities
The package also provides an `IDataAccess` interface for easy implementation of the `TStatus` type
```ts
interface IDataAccess {
result: TStatus | undefined
}
```
An example usecase could be ensuring that a DBAL class returns data in a consistent shape. In the example below a class is used to manage a resource called `Resource` (could be a User, or Item, or Post etc)
```ts
// Data Object Class file
import {IDataAccess, TStatus, statusMap} from '@neoncoder/service-response'
import { PrismaClient, User, UserCreateInput PrismaClientValidationError, PrismaClientKnownRequestError } from '@prisma/client'
class UserDBALClass implements IDataAccess {
user: User | undefined
result: TSatus | undefined
prisma: PrismaClient
constructor(){
this.prisma = new PrismaClient()
}
async create(data: UserCreateInput){
try{
const newUser = await prisma.user.create({data})
this.result = statusMap.get(201)!({data: newUser})
this.user = newUser
} catch (err){
this.formatError(err)
} finally {
return this
}
}
async update(id: string, data: UserUpdateInput){
try{
const updatedUser = await prisma.user.update({where: {id}, data})
this.result = statusMap.get(201)!({data: updatedUser})
} catch (err){
this.formatError(err)
} finally {
return this
}
}
formatError(error: unknown, msg = 'An error occurred') {
const message = error.message ?? msg
if(
err instanceof PrismaClientValidationError
||err instanceof PrismaClientKnownRequestError
) {
return this.result = statusMap.get(400)!({error, message});
}
this.result = statusMap.get(500)!({error, message})
}
}
// In Some other file
import {Request, Response} from 'express';
app.post('/users', async (req: Request, res: Response) => {
const {name, gender} = req.body
const userA = new UserDBALClass()
const {result} = await userA.create({name}).update(userA.user?.id, {gender})
const sr: ServiceResponse = result
? Rez[result.statusType]({...result})
: Rez.InternalServerError({})
return res.status(sr.statusCode).send(sr)
});
```