wett-uws is a wett api with a uws backend, this is a rest and websockets backend framework, which provides all the nessecary tools to start a backend service which inclused db, io, auth and server.
npm i wett-uws
- go check examples of server and db in examples directory in the repo you will get everthing you need there dont go to reading the spec or docs startight.
- use hozz templates with your app so you wont need to reinvent the best backend app template yourself.
- put your authentication credentials in .env file and use "require('dotenv').config();" to integrate creds in node js app.
- use the docker file provided in the repo to dockerize the app easily
- file server or sendFile in server service are not optimized for millions of requests use a production ready static file server for serving static content.
wett-uws comes with all neccesary tools to start a backend services which you can learn within 10 minutes
wett-uws server service comes with rest and websockets api's backed by uWebSockets.js its fast thats all you need to know.
https://github.com/uNetworking/uWebSockets.js/
all functions and methods of this api is defined below in code.
global.engine = require("wett-uws");
function main(){
//global rest request handler
engine.server.app.all((req,res)=>{
res.send("hello world");
});
//global websocket message handler
engine.server.app.message((channel_id,message,isBinary)=>{
console.log(message);
return true;
});
//add some route to post method
engine.server.app.post("/some",async (req,res)=>{
res.json({
hello:true
});
});
//start server
engine.server.init({
port:8080,
cors:"*",
});
}
server controller has inbuilt routing which can be routed through the following sub functions
//this route will run on all rest backends
engine.server.app.all((req,res)=>{
res.send("hello world");
});
//this is a message handler for websockets
engine.server.app.message((channel_id,message,isBinary)=>{
console.log(message);
return true;
});
//this route will only be present on "post" http requests
engine.server.app.post("/some",async (req,res)=>{
res.json({hello:true});
});
//this route will only be present on "get" http requests
engine.server.app.get("/some",async (req,res)=>{
res.json({hello:true});
});
//this route will only be present on "set" http requests
engine.server.app.set("/some",async (req,res)=>{
res.json({hello:true});
});
//this route will only be present on "delete" http requests
engine.server.app.delete("/some",async (req,res)=>{
res.json({hello:true});
});
this api only works with authenticated connections meaning this api needs to have a auth token provided for authentication before connecting to the message handler.
- generate auth token
- connect to wss socket address
- send a json object parsed as string {token:TokenString}
- wait for auth response which is parsed as a string {auth:true}
- connection is stable now
websockets connection can execute rest route handlers when the message has a websocket request schema with "at" and "id" params as a string and body param as a object itself.
**only post method route handlers are available to websockets request workers.
{
"at":"route_address",
"is":"uniqe request id",
"body":{
some:true
}
}
auth service provides a token based session identifier and a session based authetication, which means you generate a token, that token is sent to the server then the auth service runs a user defined authenticator function which gets the sessions information from user if the session exists the session data will be stored in a live object this process eliminates the logout problem of json web tokens and its extremly fast to authenticate a rest or websocket request/connection.
returns engine.common.Error or True
auth api operates on top of rsa256 public/private isgnature verification algorithm, so naturally you need rsa public/private keys to use auth api, hozz and other wett templates provides safe rsa key generation apis, you can use them or any other api to generate rsa keys.
let keys = require("./secure/keys.json");
let loadKeys = engine.auth.loadKeys(keys.private,keys.public);
if(loadKeys instanceof engine.common.Error){
return loadKeys.now()("failed-load_keys").log();
}
returns Object or False
this function is user defined authenticator it runs in authentication chain and provides data about session via session_id which is defined in session_token
engine.auth.sessions.add_auth_function((session_id)=>{
return {
session_id:session_id,
uid:'some',
channel_id:engine.md5("user"),
};
});
{Request}->
{Check_Session}->
{
{Session_Exists} -> {Run_Request}
{Session_Not_Active} -> {
{Token_Authentication}->
{User_Defined_Authentication_Function}->
{Log_Session}->
{Run_Request}
}
}
{Establish_Connection}->
{Send_Token_Message}(JSON->Parsed_as_String->{
token:{TokenString}
})->
{Check_Session}->
{
{Session_Exists} -> {Listen_For_Messages}
{Session_Not_Active} -> {
{Token_Authentication}->
{User_Defined_Authentication_Function}->
{Log_Session}->
{Listen_For_Messages}
}
}
this function verifies a token string and parses it to json data.
returns engine.common.Error or Object
let token_string = "someTokenString";
let verify = engine.auth.verify(token_string);
if(verify instaceof engine.common.Error){
return verify.now("token_authentication_failed").log();
}
this function creates a token for authentication of a rest or websocket connection/http_request
returns engine.common.Error or Token String
let make_token = engine.auth.createToken({
channel_id:engine.md5("user"),
uid:"some",
});
if(make_token instanceof engine.common.Error){
return make_token.log();
}
this json is parsed to string which is parsed ArrayBuffer which is parsed to base64 encoded string.
{
data:{
//other user defined data
//although other used defined data is not useful in provided auth protocols
session_id:"user_defined_session_identifier",
channel_id:"user defined websocket connection name/identifier",
expire:"token valility in seconds (INT)"
},
signature:"base64Encoded_Binary_as_String_RSA256_Signature"
}
this function takes a req class object and returns the auth token is present.
returns engine.common.Error or Token Data Object
engine.server.app.post("/some",async (req,res)=>{
//----------------------------------------------
//verify function
let verify = await engine.auth.verifyRequest(req);
if((verify instanceof engine.common.Error)){
return false;
}
//----------------------------------------------
res.json({
hello:true
});
});
this api keeps log of active sessions these sessions are generated with the user defined authentication function which returns a object of session data and takes in session_id as a function parameter you can check the session_id againt your database and return the session information to be kept in auth sessions api.this sessions data will be attached to the websocket connection request with the req.auth parametere and can be fetched for http rest api request by runinng engine.auth.verifyRequest api.
this function sets user decalred authentication function and takes the authentication function as a parameter.
engine.auth.sessions.add_auth_function((session_id)=>{
return {
session_id:session_id,
uid:'some',
channel_id:engine.md5("user"),
};
});
this function is user decalred authentication function, takes session id and returns a object is session is valid or false is invalid.
let authenticate = engine.auth.sessions.authenticate(session_id);
console.log({authenticate:authenticate});
this function takes a session_id and session_data as parameters and sets them to active/live sessions
engine.auth.sessions.add(session_id,{});
this function takes a session_id and return active/live session data if it exists.
engine.auth.sessions.get(session_id);
this function takes a session_id removes it from active/live sessions object.
engine.auth.sessions.delete(session_id);
db service is a abstraction layer on top of a "db specific" api layer ie it is a db syntax/api that compiles to native db syntax/api for example
//db api query
let db_query = await db.collection("users").doc("user1").get();
//mysql translation
let mysql_query = `SELECT * FROM users WHERE Doc_Name = "user1"`;
//firestore translation
let firestore_query = firestore.collection("users").doc("user1").get();
this api uses plugins to provide abstartcion layer over various databases. These plugins are available on npm and can be installed with the db install apis.
various plgins are available on npm to use.
global.engine = require("wett-uws");
//plugin for firestore
const wett_firestore = require("wett_firestore");
function main(){
//installation function
let install = await engine.db().install(wett_firestore);
if(install instanceof engine.common.Error){
return install.now("failed-install-wett-firebase-plugin").log();
}
//init database from plugin
let init = await engine.db("firestore").init({
name:'firestore',
cred:require('./creds.json'),
url:require('./dbUrl.json').daachi
});
if(init instanceof engine.common.Error){
return init.now("init failed").log(log);
}
}
this api uses a native abstraction layer so you can use diffrent kinds of databases with a single syntax throughtout your code so when you want to upgrade your application your database io wont be something to worry about.
each query saves data in collections (tables) the data is represented in docs(rows), this provides a unified approach to mysql and nosql based database storage systems
following query translates to
//db api query
let db_query = await db.collection("users").doc("user1").collection("sessions").doc("session1").insert({
name:"gzbakku",
id:1
});
//last collections becomes the table and have previous collections doc_id's as a keys.
//mysql translation
let mysql_query = `INSERT INTO sessions (users_DOC_NAME,sessions_DOC_NAME,name,id) VALUES ("user1","session1","gzbakku",1)`;
these are location and query functions that are provided by the native db api.
all query start with a collection, the collection can be queried by doc,where,orderBy,limit and after functions to add and get data from the collection.
doc is the unique identifier key to a document ie its a row id or a doc in a collection it can be followed by insert,update, increment and delete functions to manipulate its contents.
this is the primary query function you have to find the data in a collection, this function takes 3 parameter key, function and value,available functions are "==",">=","<=","<" and ">" they are usual in there operations and any number of where queries can be combindes to make a more complex query.
let get_document = await db("firestore").collection("users").where("name","==","gzbakku").where("id","==",1).where("age",">=",18).where("age","<=",36).get()
this function takes a int and returns the int number of results
let get_document = await db("firestore").collection("users").where("name","==","gzbakku").limit(5).get()
this function takes a key and "desc" or "asc" as directions and returns a array of result.
let get_document = await db("firestore").collection("users").orderBy("age","desc").get()
this function takes the last document and returns a query after given document, this document is database specific ie firestore takes a native object as the last item.
let get_document = await db("firestore").collection("users").where("name","==","gzbakku").limit(5).orderBy("age","desc").after(lastObject).get()
this function takes a doc and returns a object indicating if the document exists.
let document_exists = await db("firestore").collection("users").doc("user1").exists()
get function on collection takes a boolean input which when is true returns last document as a raw item.
let fetch_documents = await db("firestore").collection("users").where("age",">=",18).orderBy("age","desc").get(get_last_doc_as_raw);
get function on document takes a boolean input which when is true returns raw document.
let fetch_document = await db("firestore").collection("users").doc("user1").get(get_raw)
this function can be called on a collection or a document, beware this function manually deletes each document and can be slow when doing large queries and some databases dont delete the entire tree so you have to manually delete deep structures.
let document_exists = await db("firestore").collection("users").doc("user1").delete()
this function can be called on a document and inserts the object into the database.
let make_document = await db("firestore").collection("users").doc("user1").insert({
name:"gzbakku",
age:24,
id:1
});
this function can be called on a document and updates the object stored in the database.
let update_document = await db("firestore").collection("users").doc("user1").update({
id:2
});
this function can be called on a document and increments the value stored in the database by given int.
let increment_document = await db("firestore").collection("users").doc("user1").increment({
id:2//this adds 2 to the stored int value
});
this function collects queries in a transaction and performs them in a single request, some databases dont support batch queries.
let batch = engine.db("firestore").batch();
batch.collection("users").doc("user1").insert({name:"gzbakku",age:null,id:1});
batch.collection("users").doc("user1").update({age:23});
batch.collection("users").doc("user1").increment({id:1});
let commit = await batch.commit();
this function runs the transactions on the database
let batch = engine.db("firestore").batch();
batch.collection("users").doc("user1").insert({name:"gzbakku",age:null,id:1});
batch.collection("users").doc("user1").update({age:23});
batch.collection("users").doc("user1").increment({id:1});
let commit = await batch.commit();
this serive provides a fast and easy api to validate json and email data
console.log(engine.validate.email("[email protected]"));
this is a complex json data schema verification api
let schema = {
"user name":{type:"string",min:3,max:256},
"age":{type:"number",min:18,max:111},
"friends":{type:"array",min:1,max:2048,elective:true},
"address":{type:"object",validate:{
returnError:false,
dynamic:false,
maxSize:2,
schema:{
address:{type:"string",min:1,max:512},
country:{type:"string",options:["singapore","india"]}
},
}},
};
if(!engine.validate.json(schema,data,"dynamic"||"static",maxSize,returnError)){
return false;
}
these are functions associated with data types to match the value to a schema
as indicated min operator matches a string to a minimum length, array to a minimum no of items, object to a minimum number of keys and number to a minimum number of value.
as indicated min operator matches a string to a maximum length, array to a maximum no of items, object to a maximum number of keys and number to a maximum number of value.
this operator macthes a string to the given options in a array.
this operator ignores if the data is not available.this operator only works in the dynamic check.
this operator validates a children object and takes the same parameteres just in a object to validate the child object.
{
schema:{STANDARD_SCHEMA},
dynamic:{DYNAMIC_VALIDATION},
maxSize:{MAX_NO_OF_KEYS},
returnError:{RETURN_ERR_OBJECT}
}
supported data types are
- string - (min,max,options,elective)
- number - (min,max,elective)
- array - (min,max,elective)
- object - (min,max,elective,validate)
- email - (min,max,elective)
this is a object where keys point to the keys in data and takes a object with match parameters as object data.
let schema = {
"user name":{type:"string",min:3,max:256},
"age":{type:"number",min:18,max:111},
"friends":{type:"array",min:1,max:2048,elective:true},
"address":{type:"object",validate:{
returnError:false,
dynamic:false,
maxSize:2,
schema:{
address:{type:"string",min:1,max:512},
country:{type:"string",options:["singapore","india"]}
},
}},
};
this is the data object which will be validated through given schema
these are two types of validation "static" and "dynamic", default is "static", static validation matches all the keys if any keys are absent the validation failes,dynamic validation allows absent keys but only those keys who's elective property is "TRUE"
this property allows you to limit size of a data object in dynamic validationm it takes a int which it matches to the number of keys present in data object.
this proeprty takes a boolean if true it returns a object stating the field that failed match to schema and why it failed the match.
let data = {
"user name","gzbakku",
age:24,
address:{
address:"github",
country:"india"
}
};
let schema = {
"user name":{type:"string",min:3,max:256},
"age":{type:"number",min:18,max:111},
"friends":{type:"array",min:1,max:2048,elective:true},
"address":{type:"object",validate:{
returnError:false,
dynamic:false,
maxSize:2,
schema:{
address:{type:"string",min:1,max:512},
country:{type:"string",options:["singapore","india"]}
},
}},
};
if(engine.validate.json(schema,data,"dynamic",4,false)){
return false;
}
this service takes a object array or stringa dn removes html special charaters from strings.
engine.sanitize({
some:"<h1>some</h1>"
});
these are file management apis
these are directry management apis
this function returns the current corking directory.
const cwd = engine.disk.dir.cwd();
this function returns directory in which current program was executed
const app = engine.disk.dir.app();
this function ensures the dir exists if not it creates a dir in the path.
engine.disk.dir.ensure("/some_dir");
this function returns sub directories in a given directory.
const sub_dirs = engine.disk.dir.sub_dir("/some_dir");
this function returns files in a given directory.
const files = engine.disk.dir.files("/some_dir");
this function returns all children element in given directory.
const child_elements = engine.disk.dir.children("/some_dir");
this function copy given item to given location.
const copy_result = engine.disk.copy("/some_dir","/new_dir");
this function returns raw data or string of a given file.
const data = engine.disk.read("/some_dir/some_file");
this function returns JSON object from a given file.
const data = engine.disk.readJson("/some_dir/some_file.json");
this function writes data to given file.
const write = engine.disk.write("/some_dir/some_file.json",data);
this function deletes a given file.
engine.disk.delete("/some_dir/some_file.json");
this api makes time services easy you can get now,date,month,year,day from sub apis here those are usual i am not gonna explain them.only now returns getTime() method input in miliseconds what ever they are i dont remember they are time elapsed in mili-seconds since linux epoch or something check yourself.
this class makes it easy to find elapsed time from when a timer was begin.
let timer = new engine.time.Timer();
setTimeout(()=>{
//this will console log the time diffrence in mili-seconds
timer.now(true);
},3000);
these are some loggin apis including tell,error,inform and success each takes a message and a log boolean if log is true it will log the message to console.
this class makes it easy to generate a error chain it is returned by a few apis in auth and server.
let error = new engine.common.Error("some error");
//this will add another error to the error chain
error.now("new error");
//this will log the error object to the console.
error.log();