A Collection of utility functions to quickly scaffold and standardize server-side function returns and http responses with nodejs.
Yet Another HTTP Response Formatter
Table of Contents
Meant to be installed on a nodejs express server with either JavaScript or Typescript
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
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
}
- Import and invoke the generator function (e.g.
OK
,NotFound
,Created
,Unauthorized
etc) passing in none or any of theServiceResponse
type 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
Rez
object 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)
})
- The
statusMap.get(<statusCode>)
method returns a function that takes in an options object as parameter (i.e.{message, fix, error, data}
) to return aTStatus
object;
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 thestatuses
object 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)
})
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)!({}) |
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)
});