@neoncoder/service-response
v0.0.14
Published
A Collection of utility functions to quickly scaffold and standardize server-side function returns and http responses with nodejs.
Downloads
7
Maintainers
Readme
Service Response Formatter
A Collection of utility functions to quickly scaffold and standardize server-side function returns and http responses with nodejs.
Installation
Meant to be installed on a nodejs express server with either JavaScript or Typescript
npm install @neoncoder/service-responseThe package exposes types, interfaces, and functions to help standardize and easily scaffold json http responses and function returns
ServiceResponse - interface for http responses
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)
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,Unauthorizedetc) passing in none or any of theServiceResponsetype options (i.e.{message, success, data, fix, meta, error, errMessage, newAccessToken});
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
Rezobject and call the functions from there
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(<statusCode>)method returns a function that takes in an options object as parameter (i.e.{message, fix, error, data}) to return aTStatusobject;
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
statusesobject contains a predefined list of TStatus Objects that can be referenced by the Status Type. Using the previous example with thestatusesobject becomes:
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
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)
// 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)
});