A KISS JSON REST API framework that can be mounted to your Express application.
npm install autonym
See the basic example project.
Autonym is another framework built on top of Express to simplify building REST APIs for your records. However, its philosophy sets it apart from most other Node.js API frameworks.
It is extremely lightweight and written in ES6. By design, it eliminates the need to scaffold controllers in your API, because they can be inferred automatically from your models. Models are driven by simple configuration objects and in many cases can just forward their arguments to an ORM. As a result, APIs built in Autonym require little coding but still offer total control over each CRUD action for a record, and are very easy to understand at a glance.
It's just middleware. Most frameworks take over your entire application, making it difficult to adjust an existing app to the new framework's setup. This also results in endless frustration when trying to do something the framework isn't designed to do. Autonym is just mounted like any other middleware, so you can add other middleware and handlers before or after Autonym to do whatever you want, the way you normally would.
No bloat. Autonym follows the single responsibility principle and seeks to do just one thing well: map requests to CRUD actions. Following from the previous principle, if your server needs to serve static assets as well, just mount middleware beside Autonym for your other routes. If you authenticate with JWTs, simply mount your JWT middleware before Autonym.
Your API, your response. Autonym makes a habit of never sending the response to the user directly. This allows you to intercept the response to make any adjustments. If you want to let Autonym handle the response, it exposes another middleware to mount after that will send the response.
Data validation is standardized. Autonym validates JSON documents using the JSON schema spec. JSON schemas are an industry standard and, being JSON documents themselves, are completely portable and ingestable by other clients, even written in other languages. For more comprehensive validation beyond JSON schemas, Autonym allows you to define validation and sanitization policies and map them to CRUD actions.
For better or for worse, database schemas are not document schemas. In real life, there is rarely a perfect 1:1 relationship between properties on the request body and column names in a table. Autonym doesn't attempt to unify data models -- in fact, it doesn't care about databases at all! However, data store implementations are free to define mapping functions to translate documents to queries.
It has no opinion about ORMs. Many REST frameworks are tightly coupled to ORMs, but, like the previous point, we recognized that sometimes in the real world you fight ORMs more than you love them, because one size never fits all. Autonym lets you integrate them to whatever extent you want. Autonym just expects a model to implement five CRUD methods. Whether those methods forward the data to an ORM or just run some queries or ops directly is up to you. Some models might directly correspond to a Mongo collection or SQL table, while other models might be virtual representations of more complex data. That distinction is completely up to you.
A clear distinction between a programmatic API and REST API, but without controllers. Autonym splits the work of validating into two distinct phases: schemas and policies. You define them together, but policies, which only are applicable to your REST API, are not run when you just want to import your model and insert a record. This means you don't have to split up your resource definition into a separate model and controller, but you can still access your model directly without mocking a request.
Configuration is driven by plain objects. This means that every piece that makes up a model -- the methods for persisting data to your database, the combinations of policies used to transform and validate requests, and the schemas that define your record properties -- are all trivial to define, reuse, abstract, and assemble.
Error handling is a snap. Autonym ships with its own error class that allows you to throw errors like you normally would, without being conscious of when they are runtime errors or simply bad requests. Errors thrown when using the programmatic API are passed on to error-handling middleware, while errors that occur during an HTTP request are intelligently (but still explicitly) handled and returned to the client.
Embrace ES6+. Autonym app components are heavily class-based and Autonym and its sister projects are written with Babel. No more callback hell!
It's worth noting that the developers behind Autonym envisioned a simplistic data model, and as a result there are some definite drawbacks and limitations to the built-in behaviors of the framework.
Autonym has no intrinsic understanding of related records. The API does not understand foreign references and will only return record ids. This means that establishing relationships between models must be handled at the database layer or manually in the API layer. However, this eliminates some of the complexity with setting up and consuming an API with intricate routing, unintentionally costly joins, and recursive embedding.
Autonym requires all records to have a primary key that is named id. Composite primary keys or primary keys named something different are not currently supported. This is to make REST calls trivial by using standard record identifiers in the URL.
These are high-level concepts and vocabulary for working with an Autonym application.
Model: A model is an instance of the
Modelclass provided by Autonym. Constructed with a configuration object, a model defines its schema, policies, store methods, and so on. A model instance has static methods on it that make it trivial to import elsewhere in the application to create, read, update, and delete records programmatically. It's also designed to plug into the Autonym middleware to be evaluated in a HTTP request.
Schema: A schema is part of a model's configuration. It is an object that follows the JSON schema specification for defining the properties of this type of record.
Policy: A policy is a function that is run when a request hits your API for a particular model action. Policies are like Express middleware, with the added advantage that they can be easily chained together in "and" and "or" statements. They can be used to validate the request, add computed properties, and manipulate the response before it is returned to the client. Since they are just functions, ideally they are defined in other parts of the application and imported into models for easy reuse between different models.
Store methods: Store methods are the five core methods that are configured on a model that perform your CRUD operations:
findOneAndDelete. These methods are exposed on the programmatic API for the model and are called by the middleware, provided the policies permit the request. Oftentimes, these methods can be shared among many models, i.e. with an ORM. Reusable implementations of store methods are commonly referred to as stores.
res: Policies are passed request and response objects, just like Express middleware; however, these are not the standard objects provided by Express. They are wrapped in the
AutonymResclasses, which are designed to place an emphasis on identifying the user's actions, retrieving data safely, working with the model's programmatic API, and safeguarding against common programming errors.
Meta: Policies and store methods are also passed a
Model middleware: A middleware that can be mounted on your Express app. Provided with your models, it will intercept requests to your model endpoints to perform the appropriate API actions.
Responder middleware: A middleware that can be mounted on your Express app, after the
autonymmiddleware. In between the two middleware, you may install your own middleware to quash errors, add response headers, manipulate the payload, and so on. This middleware sends the response to the client.
AutonymError: A subclass of
Error. Instances of
AutonymErrorshould be thrown whenever possible from policies and store methods. These errors have preset types that will determine the status code if they are thrown during an HTTP request; if not provided a code, it will be assumed that the error message should not be enclosed in the response.