Making a ToDo list app
RESTful API
Web Socket sample
Server Sent Events (SSE)
Uploading files
How to consider Roles
contents
The sample app contains a list of tasks in an entity that stores its data in a file. It is possible to add or delete tasks.

Step 1 : Define app settings

1-1 : Create app config file

First we create a folder named todo and in it we create a file named app.json containing :
{
	"name": "ToDo App"
}

1-2 : Define an entity

As a next step, we add the schema for an entity called tasks, in config file, to 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

Then we define the storage settings. Here we decide to store data of entities in a json file named data.json next to the config file in the todo folder.
{
	"name": "ToDo App",
	"schemas": [],
	"storage": {
		"adapter": "file",
		"path": "./data.json"
	}
}

1-4 : Define an endpoint

To connect to this app we define an endpoint by type cli to send NoLang scripts to it and get responses from command line.
{
	"name": "ToDo App",
	"schemas": [],
	"storage": {},
	"endpoints": [
		{
			"type": "cli"
		}
	]
}

Step 2 : Run the app

In the command prompt, Run your app like below:
cd todo
nolang . app.json
For more guides to run app see here

Step 3 : Contract with todo app

Add new task

Now we can send NoLang Scripts to the app and get its responses.
(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

To retrieve list of tasks, we can:
{"$$schema": "tasks", "$$header": {"action": "R"}} [ENTER]              
(out)[{ "name": "task1", "progress": 0, "status": "WAIT" }]
contents

Creating a RESTful API

To create a RESTful app it is enough to have endpoints with type http:
{
	"name": "a simple restfule app with Nolang",
	"entities": [],
	"endpoints": [
		{
			"type": "http",
			"static": "./public",
			"port": 8080,
			"routes": [
				{
					"path": "/",
					"method": "post"
				}
			]
		}
	]
}
this app listens to http port 8080 with POST method. we can send Nolang's scripts to it from a browser or any http client like bellow:
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));
contents

Web Socket sample

add a route like bellow in app.json file in the http endpoint
{
	"routes": [
		{
			"path": "/ws",
			"method": "ws"
		}
	]
}
connect to the websocket server from browser:
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..')
contents

Server Sent Events sample

add a route like bellow in app.json file in the http endpoint
{
	"routes": [
		{
			"path": "/done_tasks",
			"method": "get",
			"return": {
				"$$schema": "tasks",
				"$$header": {
					"action": "R",
					"listen": true,
					"filter": {
						"status": "DONE"
					}
				}
			}
		}
	]
}
get live list of done tasks
const source = new EventSource('/done_tasks');
source.onmessage = (event)=>{
    console.log(event.data)
}
contents

Uploading files

To upload files we can use file or image type properties in entities:
{
	"$id": "entity3",
	"properties": {
		"name": {
			"type": "string"
		},
		"photo": {
			"type": "image"
		}
	}
}

config app

In app.json5 config file the http endpoints must have some items:
{
	"endpoints": [
		{
			"type": "http",
			"upload": {
				"root": "./uploads",
				"maxSize": 5000000
			},
			"port": 8080,
			"routes": [
				{
					"path": "/",
					"method": "post"
				}
			]
		}
	]
}

Uploading file from browser to server

Unlike ordinary scripts, when uploading a file it is needed to use html forms to upload, so the script must be encapsulated in 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}
contents

How to consider user roles

If you want to create a Nolang app which considers user's roles it needs:
  1. Add user section in the app.json config file, with authenticate: true
  2. Have an entity containing users' info
  3. For any entity required to consider the user access rights, have $$roles in the entity definition
  4. 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

To store information of users
{
	"$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

To introduce to user in script it needs to define it in $$header like below:
{
	"$$schema": "tasks",
	"$$header": {
		"action": "R",
		"user": {
			"username": "admin1",
			"password": "1234"
		}
	}
}
If in step 1, a 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="
		}
	}
}
If in step 1, 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"
			]
		}
	}
}