Skip to content

Latest commit

 

History

History
185 lines (135 loc) · 4.88 KB

README.md

File metadata and controls

185 lines (135 loc) · 4.88 KB

📨 Response Envelopes

API response envelopes create a uniform response structure – for success responses as well as for error responses.

This an essential part for creating end to end type safe code.

📖 Table of contents

👘 Structure

This library advocates the following very simple success response envelope:

type SuccessResponseEnvelope<PAYLOAD> = {
  success: true,
  status: number,
  payload: PAYLOAD
}

The field paylod in the actual type can be optional, if PAYLOAD extends undefined.

🧐 But why?

To understand this, you should have read the Problem Details Docs 🔗.

When comparing ProblemDetail and SuccessEnvelope, you will see, that both have the field success – the former one with false and the latter one with true.

You might think:

"This looks like an excellent discriminator field! 💡"

Indeed, it's a fantastic discriminator field!

In the next section is described, how this library enables a consumer to make use of this. 🚀

🫧 Envelope type

Let me introduce the ResponseEnvelope type:

import { SuccessEnvelope } from "./types";

type ResponseEnvelope<
  PROBLEM_DETAIL_SUPER_TYPE,
  SUCCESS_PAYLOAD
> = PROBLEM_DETAIL_SUPER_TYPE | SuccessEnvelope<SUCCESS_PAYLOAD>

An instance of type ResponseEnvelope reflects an API response with all possible errors respectively Problem Details.

And any consumer / client can work with such a response in a type safe way!

🧑‍🏫 Tutorial

🚨 Attention:

This tutorial is based on the tutorial of the Problem Details Docs 🔗.

🧶 Infer your Problem Detail Super Type

import ProblemDetailsCollection from "@problemDetails/ProblemDetailsCollection"

type ProblemDetailSuperType = ProblemDetails.infer<typeof ProblemDetailsCollection>

// ProblemDetailsSuperType.type = "unauthorized" | "internal-server-error"

⚡️ Define an API endpoint payload type

type API_GetUsers_Response = {
  email: string,
  name: string
}[]

🔌 Use the type in your backend

Express, Nest.js ... please just use the response type properly in your backend, so that never an invalid response is created. ☺️

📞 Perform a 100% type safe API call

import { ResponseEnvelopes } from "@tectonique/api-standards"

const data = await axios.get("/api/users")
  .then((response) => response.data)
  .catch((error) => error.response.data)

if ( ResponseEnvelopes.isEnvelope(data) ) {
  const envelope = data as ResponseEnvelopes.Envelope<
    ProblemDetailSuperType,
    API_GetUsers_Response
  >
  
  if ( envelope.success ) {
    console.log(
      "User email adresses:",
      evelope.payload.map(user => user.email).join(', ')
    )
  } else if ( envelope.type === "unauthorized" ) {
    throw new Error("Session expired")
  }
}

Isn't that cool? 🤩

🎭 Utilities

🧱 SuccessEnvelope create function

import { ResponseEnvelopes } from "@tectonique/api-standards"

// Without payload
const NoPayloadEnvelope = ResponseEnvelopes.success(201)

// With paylod
type User = {
  email: string,
  name: string
}

const UserEnvelope = ResponseEnvelopes.success<User>(201, {
  email: "test@test.com",
  name: "Theo Tester"
})

🔎 Check if a variable is an envelope

import { ResponseEnvelopes, ProblemDetails } from "@tectonique/api-standards"


const SuccessEnvelope = ResponseEnvelopes.success(201)

const ProblemDetail = ProblemDetails.create({
  status: 422,
  type: "malformed-data",
  title: "Malformed Data",
  detail: "Hello World",
  instance: `urn:timestamp:${new Date().getTime()}`,
})


// Check for success envelopes
console.log(ResponseEnvelopes.isSuccess(
  SuccessEnvelope
)) // > true

console.log(ResponseEnvelopes.isSuccess(
  ProblemDetail
)) // > false

console.log(ResponseEnvelopes.isSuccess({})) // > false


// Check for envelope (either success OR problem detail)
console.log(ResponseEnvelopes.isOne(
  SuccessEnvelope
)) // > true

console.log(ResponseEnvelopes.isOne(
  ProblemDetail
)) // > true

console.log(ResponseEnvelopes.isOne({})) // > false