Getting Started
Overview
This 10 minute read will cover:
- Installation
- Creating custom endpoints
- Generating Create, Read, Update, Delete endpoints
- Realtime Functionality with Socket.io
- File Uploads
Installation
Make sure you have Node installed.
- npm
- yarn
npx ezbackend init my-ezbackend
npx ezbackend init my-ezbackend --yarn
Replace my-ezbackend
with your-folder-name
or .
for current dir.
This creates a basic boilerplate for you to develop your app from, which can be found in src/index.ts
.
Running your backend
You can run the boilerplate code with:
- npm
- yarn
npm start
yarn start
You'll have a working backend running on:
A User Interface will also be generated at:
The user interface provides:
- Automated API Documentation (Which can also be used to test Endpoints)
- Database Editing Interface
Creating HTTP Endpoints
You can create an HTTP endpoints with EzApps, which are the building blocks of ezbackend.
The syntax is similar to express.js
.
The below code creates a POST
endpoint at /echo
accepts a JSON input of {'input':message}
and echos an output of {'output':message}
.
- Sample
- Full Sample
const app = new EzBackend()
const echo = new EzApp()
echo.post('/', async (req,res) => {
return {output: req.body.input}
})
app.addApp(echo, {prefix:'echo'}) // Prefix the all the EzApp routes with '/echo'
app.start() // Start the backend
import { EzBackend, EzApp } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
const echo = new EzApp()
echo.post('/', async (req, res) => {
return { output: req.body.input }
})
app.addApp(echo, {prefix:'echo'})
app.start()
info
Throughout the tutorial, if you need a fully working, copy-pastable example, you can click on Full Sample
.
To test the endpoint, we need to specify the input schema. We can do this using Typescript classes. (Or JSON Schema if you are using vanilla js)
- Sample
- Full Sample
// Use class for automated documentation
export class EchoInput {
input: string
}
const echo = new EzApp()
echo.post('/', {
body: EchoInput // Tell EzBackend to expect EchoInput as the request body
}, async (req, res) => {
return { output: req.body.input }
})
app.addApp(echo)
app.start()
import { EzBackend, EzApp } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
export class EchoInput {
input: string
}
const echo = new EzApp()
echo.post('/', {
body: EchoInput
}, async (req, res) => {
return { output: req.body.input }
})
app.addApp(echo)
app.start()
You can test your echo endpoint from the DB UI.
Defining your input and output explicitly provides:
- Input/Ouput Validation
- Automated Documentation for Frontend Engineers
- Faster Endpoints
- Typescript support within endpoint handlers
You can provide additional metadata for routes to provide better documentation for your frontend developers.
- Sample
- Full Sample
echo.post('/', {
body: EchoInput,
reply200: EchoOutput,
summary: "Echo Route",
description: "Accepts a JSON input of {'input':message} and echos an output of {'output':message}"
}, async (req, res) => {
return { output: req.body.input }
})
import { EzBackend, EzApp } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
export class EchoInput {
input: string
}
export class EchoOutput {
output: string
}
const echo = new EzApp()
echo.post('/', {
body: EchoInput,
reply200: EchoOutput,
summary: "Echo Route",
description: "Accepts a JSON input of {'input':message} and echos an output of {'output':message}"
}, async (req, res) => {
return { output: req.body.input }
})
app.addApp(echo)
app.start()
Connecting to the Database
EzBackend's built models have multiple purposes:
- As an ORM (Object Relational Mapping) library
- To generate CRUD (Create Read Update Delete) Routes
- Sample
- Full Sample
const pets = new EzModel(
"Pet", // Table Name
{
name: Type.VARCHAR,
age: Type.FLOAT,
species: {
type: Type.ENUM,
enum: ['cat','dog','rabbit']
},
}
)
app.addApp(pets, {prefix: 'pets'})
import { EzBackend, EzModel, Type } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
const pets = new EzModel(
"Pet", // Table Name
{
name: Type.VARCHAR,
age: Type.FLOAT,
species: {
type: Type.ENUM,
enum: ['cat','dog','rabbit']
},
}
)
app.addApp(pets, {prefix: 'pets'})
app.start()
What the above code does is:
- Create a table in your database called
Pet
- Create the columns in your database
name
,age
andspecies
- Create
CRUD
(Create, Read, Update, Delete) endpoints under the route prefixpets
You can test the CRUD endpoints from the DB UI.
If you wish to create a table but not the CRUD
endpoints you can use EzRepo
instead.
- Sample
- Full Sample
const pets = new EzRepo("Pet", {...})
import { EzBackend, EzRepo, Type } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
const pets = new EzRepo("Pet", {
name: Type.VARCHAR,
age: Type.FLOAT,
species: {
type: Type.ENUM,
enum: ['cat', 'dog', 'rabbit']
},
})
app.addApp(pets, { prefix: 'pets' })
app.start()
You can create your own endpoints on an EzRepo
/EzModel
because they are childs of EzApp.
- Sample
- Full Sample
pets.get('/count',{
reply200: PetCount,
summary: "Get total pet count",
description: "Get the total number of pets"
},async (req, res) => {
const totalPets = await pets.count()
return { 'totalPets': totalPets }
})
import { EzBackend, EzRepo, Type } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
export class PetCount {
totalPets: number
}
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
const pets = new EzRepo("Pet", {
name: Type.VARCHAR,
age: Type.FLOAT,
species: {
type: Type.ENUM,
enum: ['cat', 'dog', 'rabbit']
},
})
pets.get('/count',{
reply200: PetCount,
summary: "Get total pet count",
description: "Get the total number of pets"
},async (req, res) => {
const totalPets = await pets.count()
return { 'totalPets': totalPets }
})
app.addApp(pets, { prefix: 'pets' })
app.start()
In the above example, we used pets.count()
to get the total number of pets
. EzRepo
and EzModel
expose underlying typeorm Repository
methods in order to help you access data in the database.
Some other common functions you can use are create
, find
, delete
, createQueryBuilder
.
Realtime Connection
By default, the CRUD routes generated by EzModel
emit socket.io
signals whenever entities are created, updated or deleted through those endpoints.
The emitted events follow the following format:
Socket.io Namespace | Event Name | Arg 1 | Arg 2 | Caveats |
---|---|---|---|---|
/ | entity_created | Entity Name | Full Entity | Only works when using EzBackend generated Routes |
/ | entity_updated | Entity Name | Full Entity | Only works when using EzBackend generated Routes |
/ | entity_deleted | Entity Name | Full Entity | Only works when using EzBackend generated Routes |
File Storage
You can add file storage with one line of code
- Sample
- Full Sample
const person = new EzModel("Person", {
name: Type.VARCHAR,
age: Type.FLOAT,
avatar: Type.FILE // Store file metadata and accept file uploads
})
import { EzBackend, EzModel, Type } from '@ezbackend/common';
import { EzCors } from '@ezbackend/cors';
import { EzDbUI } from '@ezbackend/db-ui';
import { EzOpenAPI } from '@ezbackend/openapi';
export class PetCount {
totalPets: number
}
const app = new EzBackend();
// ---Plugins---
// Everything is an ezapp in ezbackend
app.addApp(new EzOpenAPI());
app.addApp(new EzDbUI());
app.addApp(new EzCors());
// ---Plugins---
const person = new EzModel("Person", {
name: Type.VARCHAR,
age: Type.FLOAT,
avatar: Type.FILE // Store file metadata and accept file uploads
})
app.addApp(person, { prefix: 'persons' })
app.start()
This stores the file metadata in your database, and files in your filesystem under tmp/uploads
by default.
In addition, multipart/formdata endpoints are automatically generated for you to be able to upload your files, and endpoints for downloading your files or displaying them are also available.
All data in the backend is handled with streams, meaning that any file storage functionality does not utilise an excessive amount of additional RAM.
Additional Configuration
By default, EzBackend uses SQLite as the database and the filesystem for handling uploads. However, EzBackend is fully customisable such that you can manage anything from the size of files being uploaded to the database being used, etc.
You can read more about configuration here
Shoulders of Giants
EzBackend is built on two well-known, battle tested packages, namely fastify and typeorm.
Hence, anything that is possible with fastify and typeorm is also possible within EzBackend.
However the value that EzBackend provides is the improved developer experience by providing a DRY interface
and additional functionality
on top of the two existing libraries.