Step 1 : Define app settings
1-1 : Create app config file
{
"name": "ToDo App"
}
1-2 : Define an entity
schemas
array{
"name": "ToDo App",
"schemas": [
{
"$id": "tasks",
"properties": {
"name": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"WAIT",
"IN-PROGRESS",
"DONE",
"ARCHIVED"
]
},
"progress": {
"type": "number",
"minimum": 0,
"maximum": 100
}
}
}
]
}
1-3 : Config data storage
{
"name": "ToDo App",
"schemas": [],
"storage": {
"adapter": "file",
"path": "./data.json"
}
}
1-4 : Define an endpoint
{
"name": "ToDo App",
"schemas": [],
"storage": {},
"endpoints": [
{
"type": "cli"
}
]
}
Step 2 : Run the app
cd todo
nolang . app.json
Step 3 : Contract with todo app
Add new task
(out)//send script in one line:
{"$$schema": "tasks", "$$header": {"action": "C"}, "name": "task1", "progress": 0, "status": "WAIT"} [ENTER]
(out)//response:
(out){"success": true, $$objid: 1 }
Retrieve tasks
{"$$schema": "tasks", "$$header": {"action": "R"}} [ENTER]
(out)[{ "name": "task1", "progress": 0, "status": "WAIT" }]
Creating a RESTful API
http
:{
"name": "a simple restfule app with Nolang",
"entities": [],
"endpoints": [
{
"type": "http",
"static": "./public",
"port": 8080,
"routes": [
{
"path": "/",
"method": "post"
}
]
}
]
}
let command = `{
"$$schema": "products",
"$$header": {
"action": "C"
},
"productKey": "PROD-1",
"productName": "Product 1",
"address": "Address 1"
}`;
fetch('https://localhost:8080', {
method: 'POST',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: command
}).then(res => res.json())
.then(res => console.log(res)) //res is the final result
.catch(err => console.error(err));
adding more methods
{
"endpoints": [
{
"type": "http",
"static": "./public",
"port": 8080,
"routes": [
{
"path": "/",
"method": "post"
},
{
"path": "/add",
"method": "post",
"return": {
"$$schema": "tasks",
"$$header": {
"action": "C"
},
"name": "{{env.request.body.name}}",
"progress": "{{env.request.body.progress}}"
}
},
{
"path": "/list",
"method": "get",
"return": {
"$$schema": "tasks",
"$$header": {
"action": "R"
}
}
},
{
"path": "/formSubmitTarget",
"method": "post",
"bodyParser": "urlencoded",
"$$header": {
"action": "C"
},
"Field1": "{{env.request.body.Input1}}",
"Field2": "{{env.request.body.Input2}}"
},
{
"path": "/redirectMe",
"method": "get",
"return": {
"anything": "x",
"$$res": {
"redirect": "https://any.other.url"
}
}
},
{
"path": "/responseManipulate",
"method": "get",
"return": {
"anything": "x",
"$$res": {
"cookies": {
"cookie1": "value1",
"cookie2": {
"value": "value2",
"options": {}
}
},
"clearCookie": {
"name": {
"path": "/admin"
},
"name2": {}
},
"headers": {
"Set-Cookie": "foo=bar; Path=/; HttpOnly",
"Link": [
" ",
" "
]
},
"attachment": "path/to/logo.png",
"download": "path/to/file.pdf",
"links": {
"next": "http://api.example.com/users?page=2",
"last": "http://api.example.com/users?page=5"
},
"location": "http://example.com",
"type": "jsonp",
"vary": "User-Agent",
"status": 404,
"sendFile": "/absolute/path/to/404.png"
}
}
}
]
}
]
}
The http endpoint has five routes:
The first is a POST method for sending Nolang scripts.
The second, at /add
, accepts a JSON body containing name and progress keys.
The /list
route retrieves a list of tasks via a GET request.
The /formSubmitTarget
route allows submission of an HTML form with the action set to /formSubmitTarget
, pay attention to using the bodyParser attribute.
/redirectMe
route shows if the response of a request contains redirect key, the request will be redirected to its value. For example if you call a method in request, that method can reply an object contains $$res
includes a redirect key.
Finally the /responseManipulate
route shows all options to how to manipulate the response of http using $$res
keyword. For more information see Express Response Methods.
from the browser or any js client, we can use the API like below:
//using /add
fetch('https://localhost:8080/add', {
method: 'POST',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
},
body: {
name: "new task",
status: "IN-PROGRESS"
}
}).then(res => res.json())
.then(res => console.log(res)) //res is the final result
.catch(err => console.error(err));
//using /list
fetch('https://localhost:8080/list', {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(res => document.getElementById('list').innerText = res) //res is the final result
.catch(err => console.error(err));
Web Socket sample
{
"routes": [
{
"path": "/ws",
"method": "ws"
}
]
}
if ("WebSocket" in window) {
const ws = new WebSocket("ws://localhost:1100/ws");
//to send Nolang scripts via websocket to the server:
var sendRequest = function (script){
ws.send(script);
}
//To receive Nolang server's responses:
ws.onmessage = function (evt) {
let response = evt.data.toString();
console.log(response);
};
}
sendRequest('..a Nolang script..')
Server Sent Events sample
{
"routes": [
{
"path": "/done_tasks",
"method": "get",
"return": {
"$$schema": "tasks",
"$$header": {
"action": "R",
"listen": true,
"filter": {
"status": "DONE"
}
}
}
}
]
}
const source = new EventSource('/done_tasks');
source.onmessage = (event)=>{
console.log(event.data)
}
Uploading files
file
or image
type properties in entities:{
"$id": "entity3",
"properties": {
"name": {
"type": "string"
},
"photo": {
"type": "image"
}
}
}
config app
{
"endpoints": [
{
"type": "http",
"upload": {
"root": "./uploads",
"maxSize": 5000000
},
"port": 8080,
"routes": [
{
"path": "/",
"method": "post"
}
]
}
]
}
Uploading file from browser to server
command
property like below:document.querySelector('#fileInput').addEventListener('change', event => {
handleImageUpload(event)
})
const handleImageUpload = event => {
const files = event.target.files
const formData = new FormData()
formData.append('photo', files[0]) //photo is equal to the name of the image field
formData.append('command', JSON.stringify({ //command contains the script
$$schema: 'user_proxy',
$$header: {
action: 'C'
},
name: document.getElementById('Name').value,
}))
fetch('/', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.log(data)
})
.catch(error => {
console.error(error)
})
}
After uploading file(s) and the command, the file will be stored in [upload-dir]/[entity's $id]/[id of new record]/[name of image filed] , for example ./uploads/entity3/2341/photo.png
In the database where data of the entity is stored, in the photo field, a json data will be saved containing these information: {fileName: file.name,
size: file.size,
ext: fileExt}
How to consider user roles
- Add
user
section in theapp.json config file, withauthenticate: true
- Have an entity containing users' info
- For any entity required to consider the user access rights, have
$$roles
in the entity definition - In scripts that are sent to app, add
user
information
1- In app.json config file
{
"user": {
"authenticate": true,
"schema": "users",
"usernameField": "username",
"passwordField": "password",
"rolesField": "roles",
"jwt": {
"secret": "5Tyh&8%$S-=@",
"expiredIn": "2h"
},
"directRoles": false
}
}
2- Define an entity for users' information
{
"$id": "users",
"properties": {
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"roles": {
"type": "array"
},
"extraField": {
"type": "string"
}
},
"$$storage": {
"adapter": "inline",
"data": [
{
"username": "admin1",
"password": "1234",
"roles": [
"ADMIN"
]
},
{
"username": "user1",
"password": "11123",
"roles": [
"USER"
]
},
{
"username": "superuser",
"password": "2314",
"roles": [
"ADMIN",
"USER"
]
}
]
}
}
3- Add $$roles
in entity
{
"$id": "tasks",
"properties": {
"name": {},
"status": {},
"progress": {}
},
"$$roles": [
{
"roleId": "ADMIN",
"permissions": [
{
"access": [
"A"
]
}
]
},
{
"roleId": "USER",
"permissions": [
{
"access": [
"C",
"U",
"R"
]
}
]
},
{
"roleId": "*",
"permissions": [
{
"access": [
"R"
]
}
]
}
]
}
4- User info in $$header.user
in Nolang scripts
{
"$$schema": "tasks",
"$$header": {
"action": "R",
"user": {
"username": "admin1",
"password": "1234"
}
}
}
jwt
is set in user
section of app config file, after sending the above script to the server, a token will be generated and returned. that token must be saved in the client side and used in next scripts like below:{
"$$schema": "tasks",
"$$header": {
"action": "R",
"user": {
"token": "yHSIDhsljHD1GE4U5DHL7JHu&43h3ewjkdjdfhlajfd="
}
}
}
directRoles : true
is set in user
section of app config file, roles of users can be sent directly in script. It is not a secure way and must be used just in development not in production.{
"$$schema": "tasks",
"$$header": {
"action": "R",
"user": {
"roles": [
"ADMIN"
]
}
}
}