1. Backend - Refzlund/sveltekit-zero-api GitHub Wiki

Defining an endpoint

Endpoints are the URLs where we use our methods; GET, POST, PUT etc.

These can take a body or a query. Let's define our endpoint inputs:

interface Post = {
   body: {
      productName: string
   },
   query?: {
      value?: number
   }
}

query and query.value are marked as optional using '?'. (Note: This is not validated by ZeroAPI)

Inside our endpoint, we import import type { KitEvent } from 'sveltekit-zero-api'. and we can optionally import RequestEvent to get typed params:

import type { KitEvent } from 'sveltekit-zero-api'
import type { RequestEvent } from './$types'

export async function POST(event: KitEvent<Post, RequestEvent>) {

} 

An endpoint (ex. api.product.post) must return a response. Due to how Sveltekit Zero API works, it must be a type returned from sveltekit-zero-api/http. (All generalized HTTP responses are covered)

import type { KitEvent } from 'sveltekit-zero-api'
import type { RequestEvent } from './$types'
import { Ok, BadRequest, InternalError } from 'sveltekit-zero-api/http'

interface Post = {
   body: {
      productName: string
   }
}

export async function POST(event: KitEvent<Post, RequestEvent>) {
   // We get our typed body:
   const { productName } = await event.request.json()

   if(ok)
      return Ok({ body: { message: 'Product posted' }})
   return BadRequest()
} 



Generic endpoints

Sometimes you might want to type your endpoint method response in relation to the input. You can reference the input request as a type via generics:

interface Post = {
   body: {
      productName: string
   }
}

export async function POST<const Request extends Post>(event: KitEvent<Request, RequestEvent>) {
   // We get our typed body:
   const { productName } = await event.request.json()

   const data = db.getProduct({ name: productName })

   if(data)
      return Ok({ 
         body: { 
            productName: productName as Request['body']['productName'],
            data
         }
      })
   return BadRequest()
} 
Example of what that would look like API Endpoint:

API Endpoint

Frontend:

Frontend



querySpread

A handy utility function is querySpread — it will make an object of your queries, and attempt to format into their respective types.

import type { KitEvent } from 'sveltekit-zero-api'
import type { RequestEvent } from './$types'
import { querySpread } from 'sveltekit-zero-api'

interface Post = {
   query: {
      age: number
      name: string
      permitted: boolean
      parents: {
         mother: name
         father: name
      }
   }
}

export async function POST(event: KitEvent<Post, RequestEvent>) {
   // We get our typed body:
   const { 
      age,
      name,
      permitted,
      parents: {
         mother = "Not provided.", // Default value, if value is undefined
         father = "Not provided."
      } = {} // Default value of 'parents'
   } = querySpread(event)
   ...
} 

If these queries are present, they can be accessed with or without a deconstructor. The querySpread by default, formats the values using a formatting determination algorithm. This does not validate the specific type, so remember to do type checking. You can opt-out on the formatting using second parameter; querySpread(event, false)


Formatting determination

"abc"        => "abc"
"123.12"     => 123.12      // Only contains numbers
"$123.123"   => "$123.123"  // NaN
"123.12.12"  => "123.12.12" // NaN
"true"       => true
"TRUE"       => "TRUE"      // Booleans has to be lowercase
"false"      => false
"undefined"  => undefined
"null"       => null
"NULL"       => "NULL"      // `null` and `undefined` has to be lowercase
"{...}":     => {...}
"[...]"      => [...]
"2022-05-06T22:15:11.244Z"   => new Date("2022-05-06T22:15:11.244Z") // Only accepts ISO-date strings (i.e. `new Date().toISOString()`) 
'"2022-05-06T22:15:11.244Z"' => new Date("2022-05-06T22:15:11.244Z") // Has quotes around the ISO-string (from `new Date()`)




Error Handling

Warning This is deprecated and will be removed in 1.0.0.

Instead, please make use of endpointPipe

Handling errors in backend can be typed using another utility function of SvelteKit Zero API: err ... yeah just err.

import type { KitEvent } from 'sveltekit-zero-api'
import type { RequestEvent } from './$types'
import { querySpread, err } from 'sveltekit-zero-api'

export async function POST(event: KitEvent<Post, RequestEvent>) {
   const { message } = await event.request.json()

   const query = querySpread(event)
   // We can't refer `const query` directly to `err.require`,
   // since if query.boink is undefined, it will not be in the object in the first place.
   const { boink, test } = query

   // We define variable for our `err.handler`
   let errorResponse = err.handler(
      // Requires these values to be present
      err.require({ boink, test, message }),

      // Requires values of object query, to have these types (via typeof)
      err.type(query, { boink: 'string', test: 'number' }),

      // (0) If a condition is failed, will result in the (1) error message:
      err.test(boink?.length > 2, { boink: 'Must be longer than 2 characters' }),

      // It matches (0) each value of each key, to the (1) Regex expression, and (2) errors if it fails:
      err.match({ message }, /Giraffe/g, 'Must include the word "Giraffe"')
   )

   if (errorResponse)
      // if `errorResponse` exists, then we can return it with a parameter of the status we'd return:
      return errorResponse('BadRequest')

   ...

The front-end response will be a BadRequest, with

{
   body: {
      errors: {
         boink?: string
         message?: string
         test?: string
      },
      message?: string
   }
}

The error properties are optional, since they only may error. If a key (ex. boink), fails multiple times, the first one will always show. boink will therefore show "Required", before "Must be longer than 2 characters".


The err object

This object is a collection of functions to handle errors.

err.handler(...args: (Record<any,any> | null)[])

If there are any record objects in the arguments, it will result in an error-response.

Functions will therefore return either an object or null.


Example  This will always result in an error:
err.handler({ name: 'Name was not specified' })


err.require(obj: Record<any, any>, errorMsg?: string)

Takes in an object, and checks whether each item is undefined or null.


err.type(obj: Record<any, any>, types: Record<keyof obj, string> | string)

Will check each key of obj, to see if it matches the assigned type.


err.test(condition: any, errors: Record<any, any>)

If condition fails, will result in the errors specificed


err.match(obj: Record<any, any>, RegExp | Record<keyof obj, RegExp>, errorMsg?: string)

Tests each value of obj of a RegExpression, if it fails, it will result in the specified error.

⚠️ **GitHub.com Fallback** ⚠️