Note This app is still in the early stages of development.
This is an express middleware package containing features of a rest api. The main purpose is to create application without having to deal with the core functionality like authentication, account management and other essential features of an account base system. Your responsibility will be focus only to the application content.
Please make sure that you already have software dependencies installed before you proceed, the versions we use at the time of developement are: Node v18.13.0 and MongoDB 6.0.8.
Softwares | Languages | Packages |
---|---|---|
Node |
Javascript/Typescript |
Mongoose |
MongoDB |
Express |
A cli is available for creating and administering kagiweb-tech projects. To create a server using @kagiweb-tech/api-core-a middleware execute the snippet below on your terminal.
> npm i -g @kagiweb-tech/cli
> kwtech
- select
App Creator
- select
Create - API Core A (Ts)
- enter the project name
This will create a new project folder in the current directory containing the initial codebase. To execute the project, first get inside the project folder and install the packages by executing npm i
on the terminal.
At this point, the application is still not usable because the database is empty. To populate the database with initial data. Goto the project folder where the .env file is located and open the terminal, then execute the snippet below.
// if @kagiweb-tech/cli not yet installed
// just execute: npm i -g @kagiweb-tech/cli
> kwtech
- select
Administrative Tasks
- enter admin confirm key from .env
- select
DB - seed
After seeding, you can check your mongodb if it was successfully populated with data.
This is up to you on how you start the application. Just make sure you have imported and use the library in the right way. However if you use @kagiweb-tech/cli
to setup your project, you can start the development by excuting npm run dev
and npm start
to run the build version.
A account is created during seeding. This account has a Master
Role and it has absolute access to all endpoints. The default credential for this account is shown below.
nameId: master
password: Master101!
Please continue on the next section tobe able to signin and explore the available endpoints.
After running your app on your local machine, you can access the swagger api documentation on route http://localhost:5001/api/v1/apidoc/#/
.
If you had customization in .env file, then the route is on http://localhost:{ APP_PORT }{ ROOT_API_ENDPOINT }apidoc/#/
where APP_PORT and ROOT_API_ENDPOINT are values from .env file.
Note We recommend going into api documentation route after setting up and running your application. There, you will see all the available routes and detailed documentation provided by this package.
Warning Please note that the examples are written in typescript, however you can just convert the code to javascript if you are using plain JS.
There are no special syntax for creating routes. You have freedom to create your own as long as it is a valid express route. In this tutorial, we use ready made utilities from this package, but this are optional.
ErrorHandler
is just a wrapper for handling errors and routerIdentity
is just an object that will let the application register your custom routes to features collection.
import express from 'express'
import ErrorHandler from '@kagiweb-tech/api-core-a/utils/errorHandler'
import routerIdentity from '@kagiweb-tech/api-core-a/utils/routerIdentity'
import NoteModel, { INote } from './noteModel'
const router = express.Router()
router.get('/api/v1/notes', async (req, res) => {
const [result, statusCode] = await ErrorHandler.execute<INote[]>(async () => {
return await NoteModel.find()
})
return res.status(statusCode).send(result)
})
router.post('/api/v1/notes', async (req, res) => {
const [result, statusCode] = await ErrorHandler.execute<INote>(async () => {
return await NoteModel.create({
title: 'good',
description: 'job'
})
})
return res.status(statusCode).send(result)
})
routerIdentity.addAppRouteObj(router)
export default router
to register the custom route to your express app, refer to the code below.
import appHandler from '@kagiweb-tech/api-core-a'
import noteRoutes from './noteRoutes'
import taskRoutes from './taskRoutes'
...
// register routes
// register public routes
appHandler.addPublicRoute(noteRoutes)
// register private routes
appHandler.addPrivateRoute(taskRoutes)
...
As long as it is a valid express middleware it will be good enough. Refer to the code below to properly register your custom middleware.
import appHandler from '@kagiweb-tech/api-core-a'
import customMiddleware1 from './customMiddleware1'
import customMiddleware2 from './customMiddleware2'
...
// register middlewares
// register public middlewares
// this will be executed before access checker
appHandler.addPublicMiddleware(customMiddleware1)
// register private middlewares
// this will be executed after access checker
appHandler.addPrivateMiddleware(customMiddleware2)
...
The same as routes, no special syntax is required to create models as long as it is a valid mongoose model.
import { Schema, model, Document, Types } from 'mongoose'
import { randomUUID } from 'crypto'
interface INote {
_id?: string,
title?: string
description?: string
}
const NoteSchema = new Schema<INote>({
_id: { type: String, default: () => randomUUID() },
title: { type: String, required: false},
description: { type: String, required: false },
}, { timestamps: true })
// create model
const NoteModel = model<INote>('Note', NoteSchema)
export {
INote
}
export default NoteModel
You can just create you own controllers, or just manage your data process inside the routes, its up to you to decide.
The application emits important events that you could listen. Example is when you request to forgot-password endpoint in which the app will generate otp key that will be use to reset the password. This will let you do anything you want with the otp such as sending it through email or phone.
Event Name | Fields | Description |
---|---|---|
otp |
module, device, ip, account, lt | when, signing in, password reset and more in the future |
account-created |
createdAccount, module, password | When a account is created via signup or by admin |
account-changed |
changedAccount, property, value, previousValue | When a accounts top level properties are changed |
workspace-update |
action, account, assignedAccount, workspace | When a account is added or removed from a workspace |
...
import mongoose from 'mongoose'
import appHandler, { appEvents } from '@kagiweb-tech/api-core-a'
const env = appHandler.getEnv()
const appRoutes = appHandler.getAppRoutes()
const app = express().use(appRoutes)
// bind app event callbacks
appEvents.on('otp', (data) => {
if (data.module === 'signin') {
console.log(`otp: signin, ${ data.lt.key } key will be sent to ${ data.lt.recipient }`)
// do something you want
} else if (data.module === 'reset-password') {
console.log(`otp: reset-password, ${ data.lt.key } key will be sent to ${ data.lt.recipient }`)
// do something you want
}
})
appEvents.on('account-created', (data) => {
if (data.module === 'signup') {
console.log(`account-created, module: ${ data.module }, nameId: ${ data.createdAccount?.nameId }`)
// do something you want
} else if (data.module === 'admin') {
console.log(`account-created, module: ${ data.module }, defaut password is: ${ data.password }, nameId: ${ data.createdAccount?.nameId }`)
// do something you want
}
})
appEvents.on('account-changed', (data) => {
if (data.property === 'nameId') {
console.log(`account-changed: ${ data.property }, value: ${ data.value }, previousValue: ${ data.previousValue }, nameId: ${ data.changedAccount?.nameId }`)
// do something you want
} else if (data.property === 'disabled') {
console.log(`account-changed: ${ data.property }, value: ${ data.value }, previousValue: ${ data.previousValue }, nameId: ${ data.changedAccount?.nameId }`)
// do something you want
} else if (data.property === 'verified') {
console.log(`account-changed: ${ data.property }, value: ${ data.value }, previousValue: ${ data.previousValue }, nameId: ${ data.changedAccount?.nameId }`)
// do something you want
}
})
appEvents.on('workspace-update', (data) => {
if (data.action === 'add') {
console.log(`workspace-update: ${ data.assignedAccount.nameId } was added to ${ data.account.nameId } -> ${ data.workspace.name }`)
// do something you want
} else if (data.action === 'remove') {
console.log(`workspace-update: ${ data.assignedAccount.nameId } was removed from ${ data.account.nameId } -> ${ data.workspace.name }`)
// do something you want
}
})
// start express application
app.listen(env.AppPort, async () => {
await mongoose.connect(env.MongoURI? env.MongoURI: '', {
dbName: env.DBName
})
...
On your router Request Object there are useful data provided by this app. Depending on the level your router is, you can access this data on the request object.
Field Name | Description |
---|---|
userAgentInfo |
the device that accessing the endpoint. This data is available from level 2 and above |
accountData |
the account that accessing the endpoint. This data is available from level 4 and above |
import express from 'express'
const router = express.Router()
router.get('custom/route', async (req:any, res) => {
// only available if this route is registered in level 2 and above
console.log(req.userAgentInfo)
// only available if this route is registered in level 4 and above
console.log(req.accountData)
console.log(req.accountActiveWorkspace)
return res.send({})
})
...
The entire application Contains 5 main levels. Each level accepts router objects.
The Main object implements the app levels. It will register the routes and middlewares into the right levels. To access the main object just import the library.
import express from 'express'
// appHandler is the main Object
import appHandler from '@kagiweb-tech/api-core-a'
const router = express.Router()
...
Each level has its corresponding implementation in the main object methods.
-
Static level
- Pubic static assets such as images, and other types of files.// route is url path, // directory is the folder that contains static files you want to be served appHandler.addPublicStaticRoute( router.use('/route', express.static('directory')) )
-
Public Middleware
- Middleware that will be executed before access checking.// customMiddleware is an express middleware appHandler.addPublicMiddleware(customMiddleware)
-
Public Routes
- Routes that are accessable to public// customRoute is an express route appHandler.addPublicRoute(customRoute)
-
Private Middleware
- Middleware that will be executed after access checking.// customMiddleware is an express middleware appHandler.addPrivateMiddleware(customMiddleware)
-
Private Routes
- This are routes that account needs access rights before being able to use.// customRoute is an express route appHandler.addPrivateRoute(customRoute)
Mongoose models that are the bases of the core application
-
Feature - this contains all features that will be the bases of access provider.
- @kagiweb-tech/api-core-a/models/featureModel
-
Role - This give accounts access to features.
- @kagiweb-tech/api-core-a/models/roleModel
-
Account - application account.
- @kagiweb-tech/api-core-a/models/accountModel
Each models has one or more controllers. This are reusable data processors for the models.
-
Feature
- @kagiweb-tech/api-core-a/controllers/featureController
-
Role
- @kagiweb-tech/api-core-a/controllers/roleController
- @kagiweb-tech/api-core-a/controllers/roleFeatureController
-
Account
- @kagiweb-tech/api-core-a/controllers/accountClientDeviceAccessTokenController
- @kagiweb-tech/api-core-a/controllers/accountClientDeviceController
- @kagiweb-tech/api-core-a/controllers/accountContactInfoController
- @kagiweb-tech/api-core-a/controllers/accountController
- @kagiweb-tech/api-core-a/controllers/accountLimitedtransactionController
- @kagiweb-tech/api-core-a/controllers/accountPasswordController
- @kagiweb-tech/api-core-a/controllers/accountRoleController
- @kagiweb-tech/api-core-a/controllers/accountAccountInfoController
- @kagiweb-tech/api-core-a/controllers/workspaceController
- @kagiweb-tech/api-core-a/controllers/workspaceAccountsController
- @kagiweb-tech/api-core-a/controllers/accountWorkspaceController
- @kagiweb-tech/api-core-a/controllers/accountWorkspaceAccountRefController
-
Authentication
- @kagiweb-tech/api-core-a/controllers/authController
-
System
- @kagiweb-tech/api-core-a/controllers/systemInfoController
-
config - handles the .env configuration and default values if empty in .env
- @kagiweb-tech/api-core-a/utils/config
-
dataCache - handles the server data caching.
- @kagiweb-tech/api-core-a/utils/dataCache
-
dataCleaner - cleans some types of data.
- @kagiweb-tech/api-core-a/utils/dataCleaner
-
dataQuery - handles some server to database queries with integrated data caching.
- @kagiweb-tech/api-core-a/utils/dataQuery
-
encryption - password hashing and checking, base64 encoding and decoding, jwt generation and more.
- @kagiweb-tech/api-core-a/utils/encryption
-
errorHandler - handles errors and output a specific format for easy usage in the router.
- @kagiweb-tech/api-core-a/utils/errorHandler
-
logger - saves log messages into a file.
- @kagiweb-tech/api-core-a/utils/logger
-
routerIdentity - this will identify routes and let the application register it to feature collection.
- @kagiweb-tech/api-core-a/utils/routerIdentity