Skip to content
This repository was archived by the owner on Aug 14, 2023. It is now read-only.

minenwerfer/semantic-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Semantic API

“An idiot admires complexity, a genius admires simplicity, a physicist tries to make it simple, for an idiot anything the more complicated it is the more he will admire it, if you make something so clusterf****d he can't understand it he's gonna think you're a god cause you made it so complicated nobody can understand it. That's how they write journals in Academics, they try to make it so complicated people think you're a genius” — Terry A. Davis

Introduction

Semantic API is a REST framework that focuses on developer experience and simplicity.

Features

  • World class developer experience using the latest TypeScript features
  • Out of the box authentication, file management, logging, rate limiting & more
  • Minimal code surface, meaning more productivity and an also minimal attack surface
  • Every use case can be trivially accessed for scripting and unit testing
  • Output your entire API as a single auto-executable JS file
  • Tiny dependency graph

Quick start

Semantic API lets you define your collections using a JSON schema superset called Description. The defineDescription function is strongely typed, and thanks to the latest TypeScript releases we can catch unintended choices such as setting an unexisting property as required. Trying to define a property that doesn't follow default casing will also result in a TS error as a way to ensure consistency. The function will also build a type from the object literal you can use later in your project.

import { defineDescription } from '@semantic-api/api'

const [Pet, description] = defineDescription({
  $id: 'pet',
  required: [
    'name'
    // error! '"age"' is not assignable to keyof TDescription["properties"]
    // 'age'
  ],
  properties: {
    name: {
      type: 'string'
    },
    tags: {
      type: 'array',
      items: {
        enum: [
          'dog',
          'cat',
          'bird'
        ]
      }
    },
    picture: {
      $ref: 'file'
    }
    // error! '"camelCase"' is not assignable to Lowercase<string>.
    // camelCase: {
    //   type: 'string'
    // }
  }
})

const pet: typeof Pet = {
  name: 'Thor',
  // error! '"dogx"' is not assignable to '"dog" | "cat" | "bird"'
  // tags: [
  //    'dogx'
  // ]
}

export default () => ({
  item: Pet,
  description,
  functions: {}
})

Next thing upon creating a collection is adding functions to it. That is, CRUD operations, actual business logic, you name it. Semantic API endpoint functions are standardized as two-parameter functions that resolve a JSON-serializable object or a Response object. Instead of the traditional function (req, res, next): void we have a "payload" parameter that represents the HTTP request body, and a "context" parameter which is a strongely typed object that contains every API resource.

import type { description } from './description'

const get = (name: string, context: Context<typeof description>) => {
  const pet = await context.model.findOne({ name }).lean()

  console.log(pet.name)
  // error! 'age' property doesn't exist on Pet.
  // console.log(pet.age)

  return pet
}

You have your resources set up, now you have to handle the access to them. Semantic API comes with Authentication and RBAC (Role-based Access Control) out of the box, so this can easily be done (and typed) in a monolithic and declarative fashion instead of having middlewares.

import { defineAccessControl } from '@semantic-api/access-control'

export const accessControl = defineAccessControl<Collections, Algorithms>()({
  roles: {
    guest: {
      inherit: [
        'unauthenticated',
        // Trying to inherit from an unexisting role will produce TS diagnostics.
        // 'unexisting_role'
      ],
      capabilities: {
        pet: {
          functions: [
            'get',
            // The below would also produce TS diagnostics because thanks to
            // the autogenerated declaration file we know that the Pet collection
            // hasn't such function.
            // 'unexisting_function'
          ]
        }
      },
    },
  },
})()

Now you just have to put it all together and start listening on a webserver. You could use your own, but Semantic API ships its own made on top of Hapi for convenience. Being like that all the server layer is compatible with Hapi.

import { initWithDatabaseThenStart } from '@semantic-api/server'
import pet from './collections/pet'

// Motice how for the sake of typing we don't pass our config down to the API
// through a function parameter. Instead, we export them from the endpoint so the
// types can be recovered globally.
export { accessControl } from './infrastructure/accessControl'

// You could even declare your collections inline here.
export const collections = {
  pet
}

// As we don't provided a `MONGODB_URI` environment variable Semantic API will
// start a runtime-only database.
initWithDatabaseThenStart()

Leveling up

License

Semantic API is MIT licensed.

About

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published