EzApp - Basic Routing
Overview
The EzApp is the building block
of an EzBackend, and you can create routes on them as you wish.
EzApps can have children which inherit options from the parent
- If a parent has a route-prefix 'user'
- All child app routes with have route prefix 'user'
Everything in EzBackend is derived from EzApp
- All Plugins are EzApps
- EzBackend is an EzApp
- EzModel is an EzApp
- EzRepo is an EzApp
info
If all you need are Create, Read, Update, Delete (CRUD) endpoints for your database, refer to the EzModel docs instead
Sample structure
The sample below creates a get, post, patch and delete request at the url /v1/users/details
- Sample
- Full Sample
const app = new EzBackend()
const v1 = new EzApp()
const users = new EzApp()
const details = new EzApp()
app.addApp(v1,{prefix:'v1'})
v1.addApp(users,{prefix:'users'})
users.addApp(details,{prefix:'details'})
details.get(...)
details.post(...)
details.patch(...)
details.delete(...)
app.start()
import {EzApp, EzBackend} from "@ezbackend/common"
import { EzOpenAPI } from "@ezbackend/openapi";
import { EzDbUI } from "@ezbackend/db-ui";
import { EzCors } from "@ezbackend/cors";
const app = new EzBackend();
//---Plugins---
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
//---Plugins---
const v1 = new EzApp()
const users = new EzApp()
const details = new EzApp()
app.addApp(v1,{prefix:'v1'})
v1.addApp(users,{prefix:'users'})
users.addApp(details,{prefix:'details'})
details.get('/',async (req,res) => {
return {name: "Robert"}
})
details.post('/',async (req,res) => {
return {created: true}
})
details.patch('/',async (req,res) => {
return {updated: true}
})
details.delete('/',async (req,res) => {
return {deleted: true}
})
app.start()
Viewing Available Routes
All created routes are automatically documented and displayed in the db-ui
You can access the db-ui at http://localhost:8000/db-ui/ (Assuming port 8000)
Creating routes
Creating a route in ezbackend can be done similarly to express or fastify.
There are three ways each route can be created:
- Async shorthand
- Sync shorthand
- Full Declaration
Get
Create an endpoint on the app which returns a json when a GET
request is performed
- Async
- Sync
- Full
export class Response {
data: string
}
app.get("/",{
reply200: Response
}, async (req,res) => {
return {data: "data"}
});
export class Response {
data: string
}
app.get("/",{
reply200: Response
}, function (req,res) {
res.send({data: "data"})
});
export class Response {
data: string
}
app.route({
method: GET, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: "data"}
},
reply200: Response
})
Post
Create an endpoint on the app which returns a json when a POST
request is performed
- Async
- Sync
- Full
export class Person {
name: string
}
export class Response {
data: Person
}
app.post("/",{
body: Person
reply200: Response
}, async (req,res) => {
return {data: req.body}
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.post("/",{
body: Person
reply200: Response
}, function (req,res) {
res.send({data: req.body})
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.route({
method: POST, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
body: Person
reply200: Response
})
Put
Create an endpoint on the app which returns a json when a PUT
request is performed
- Async
- Sync
- Full
export class Person {
name: string
}
export class Response {
data: Person
}
app.put("/",{
body: Person
reply200: Response
}, async (req,res) => {
return {data: req.body}
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.put("/",{
body: Person
reply200: Response
}, function (req,res) {
res.send({data: req.body})
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.route({
method: PUT, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
body: Person
reply200: Response
})
Patch
Create an endpoint on the app which returns a json when a PATCH
request is performed
- Async
- Sync
- Full
export class Person {
name: string
}
export class Response {
data: Person
}
app.patch("/",{
body: Person
reply200: Response
}, async (req,res) => {
return {data: req.body}
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.patch("/",{
body: Person
reply200: Response
}, function (req,res) {
res.send({data: req.body})
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.route({
method: PATCH, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
body: Person
reply200: Response
})
Delete
Create an endpoint on the app which returns a json when a DELETE
request is performed
- Async
- Sync
- Full
export class Person {
name: string
}
export class Response {
data: Person
}
app.delete("/",{
body: Person
reply200: Response
}, async (req,res) => {
return {data: req.body}
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.delete("/",{
body: Person
reply200: Response
}, function (req,res) {
res.send({data: req.body})
});
export class Person {
name: string
}
export class Response {
data: Person
}
app.route({
method: DELETE, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
body: Person
reply200: Response
})
Route Validation / Documenting Routes
EzBackend allows you to use TypeScript classes for route validation.
- Sample
- Full Sample
export class Body {
var1: string,
var2?: number,
var3: boolean,
var4: 'enum1' | 'enum2'
}
app.post('/', {
querystring: QueryString,
headers: Headers,
body: Body,
reply200: Reply,
summary: "A summary of the endpoint",
description: "A description of the endpoint"
}, async (req,res) => {
...
})
import { EzApp, EzBackend } from '@ezbackend/common';
import { EzOpenAPI } from '@ezbackend/openapi';
export class Person {
name: string
age: number
gender: 'male' | 'female'
}
const app = new EzBackend();
//Plugins
app.addApp(new EzOpenAPI());
const genderSwapper = new EzApp()
genderSwapper.post('/', {
querystring: Person,
headers: Person,
body: Person,
reply200: Person
}, async (req,res) => {
const newPerson = {...req.body}
if (newPerson.gender === 'male') {
newPerson.gender = 'female'
} else {
newPerson.gender = 'male'
}
return newPerson
})
app.addApp(genderSwapper,{prefix: 'gender-swap'})
app.start();
By specifying the input using classes, you get:
- Automatic Documentation
- Input/Output Validation
- Typescript type safety within the handler
info
If you see the warning no auto documentation nor input validation provided
, it means that you have to specify Typescript classes or JSON Schema for automatic documentation to work.
Schema Definition
To use a schema within EzBackend it needs to be exported from the top level file.
For this reason, all schemas used within a single EzBackend project need to have unique names as well.
export class Person {
name: string;
age: number;
gender: 'male' | 'female';
}
Additional JsDoc annotations can also be used to extend the default types
// This adds a format:email field in the generated schema, which is reflected in the auto-generated-docs
export class Person {
/**@format email */
email: string;
}
After creating a class you can use it in your routes for validation and automatic documentation. All the possible endpoints types are listed below. Currently Typescript Class validation only supports 200
and 400
type responses, if you need to define it for other error codes you can use JSON Schema
export class QueryString {data:any}
export class Headers {data:any}
export class Body {data:any}
export class Params {data:any}
export class Reply200 {data:any}
export class Reply400 {data:any}
app.post('/', {
body: Body,
params: Params,
querystring: QueryString,
headers: Headers,
reply200: Reply200,
reply400: Reply400,
summary: 'summary',
description: 'description'
}, async (req,res) => {
return {}
})
EzBackend uses ts-json-schema-generator to convert Typescript classes to the appropriate types.
Summary
You can provide a summary of what your route, which will be reflected in the Db-UI
- Async
- Sync
- Full
app.get("/",{
summary: "Server Time"
}, async (req,res) => {
const time = new Date().toLocaleTimeString()
return {time: time}
});
app.get("/",{
summary: "Server Time"
}, function (req,res) {
const time = new Date().toLocaleTimeString()
res.send({time: time})
});
undefined
app.route({
method: GET, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
const time = new Date().toLocaleTimeString()
return {time: time}
},
summary: "Server Time"
})
Description
You can provide a detailed description of what your route does, which will be reflected in the Db-UI
- Async
- Sync
- Full
app.get("/",{
summary: "Server Time",
description: "This method returns the current server time"
}, async (req,res) => {
const time = new Date().toLocaleTimeString()
return {time: time}
});
app.get("/",{
summary: "Server Time",
description: "This method returns the current server time"
}, function (req,res) {
const time = new Date().toLocaleTimeString()
res.send({time: time})
});
undefined
app.route({
method: GET, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
const time = new Date().toLocaleTimeString()
return {time: time}
},
summary: "Server Time",
description: "This method returns the current server time"
})
Schema Caching
When NODE_ENV
is not set to production
, json-schemas of the exported classes are stored in a auto-generated folder, schemas
The folder schemas
should be treated as code and committed in version control.
Running in Production
When NODE_ENV
is set to production
, instead of generating the json-schema on-the-fly (which is the default for non-production mode), validation schemas are obtained from the schemas
folder to reduce startup time.
JSON Schema
If you find any limitations with the automatic schema generation, or you are not using typescript, you can fall back to JSON Schema for route validation.
The schemas provided must be in JSON Schema format.
body
- Validates the request body for POST
,PUT
and PATCH
methods
query
- Validates the request query string (e.g api.your-app.com?name=bob&age=25)
params
- Validates the params (e.g api.your-app.com/user/:userId)
response
- Filters the output, according to status code (e.g if the status is 200 OK, it will follow the 200 schema)
Defining these values will also populate the documentation automatically
- Async
- Sync
- Full
app.post("/",{
schema:{
body: <YOUR JSON SCHEMA>,
query: <YOUR JSON SCHEMA>,
params: <YOUR JSON SCHEMA>,
response:
200: <YOUR JSON SCHEMA>
}
}, async (req,res) => {
return {data: req.body}
});
app.post("/",{
schema:{
body: <YOUR JSON SCHEMA>,
query: <YOUR JSON SCHEMA>,
params: <YOUR JSON SCHEMA>,
response:
200: <YOUR JSON SCHEMA>
}
}, function (req,res) {
res.send({data: req.body})
});
undefined
app.route({
method: POST, //Can be array of methods
url: '/',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
schema:{
body: <YOUR JSON SCHEMA>,
query: <YOUR JSON SCHEMA>,
params: <YOUR JSON SCHEMA>,
response:
200: <YOUR JSON SCHEMA>
}
})
Configuration
You can configure the automatic documentation functionality by setting certain environment variables
These values can be set in .env
and will automatically be loaded
Variable | Description | Sample |
---|---|---|
ENTRY_POINT_PATH | The path of the file from which all your types are exported from | ENTRY_POINT_PATH=src/index.ts |
TS_CONFIG_PATH | The path of your tsconfig.json | TS_CONFIG_PATH=tsconfig.json |
SCHEMA_DIR | The folder name where schemas are stored | SCHEMA_DIR=schemas |
Route options
Route options can be used to add additional functionality to your routes
prefixTrailingSlash
prefixTrailingSlash: string used to determine how to handle passing / as a route with a prefix.
both (default)
: Will register both /prefix and /prefix/.
slash
: Will register only /prefix/.
no-slash
: Will register only /prefix.
- Async
- Sync
- Full
app.post("/prefix",{
prefixTrailingSlash:'both' //'both'|'slash'|'no-slash'
}, async (req,res) => {
return {data: req.body}
});
app.post("/prefix",{
prefixTrailingSlash:'both' //'both'|'slash'|'no-slash'
}, function (req,res) {
res.send({data: req.body})
});
undefined
app.route({
method: POST, //Can be array of methods
url: '/prefix',
handler: async => (req, res) { //sync version works too
return {data: req.body}
},
prefixTrailingSlash:'both' //'both'|'slash'|'no-slash'
})
Route Prefixing
When adding apps to apps, you can specify the prefix in the options in order to prefix all the routes in the app and all children apps
- Sample
- Full Sample
app.addApp(childApp, { prefix: "hello-world" });
import { EzBackend, EzApp } from "@ezbackend/common";
const app = new EzBackend();
const childApp = new EzApp();
childApp.get("/", async (req, res) => {
return { hello: "world" };
});
app.addApp(childApp, { prefix: "hello-world" }); //PREFIX HERE
app.start();
Additional Functionality
EzApps are expose the functionality of fastify objects, so anything that would work in fastify would also work here.