“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
Semantic API is a REST framework that focuses on developer experience and simplicity.
- 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
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()
- Read the official documentation
- Take a look at some neat examples
- Join our Discord community
- Ready to participate? Read the Contributing Guide
Semantic API is MIT licensed.