Repository: lwinterface/panini (language: python)

Issue:
updated documentation



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c6e210d..a5f1f36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,236 +1,77 @@
 # Changelog
+## [v0.6.2](https://github.com/lwinterface/panini/tree/v0.6.2) (2021-11-11)
 
-## [Unreleased](https://github.com/lwinterface/panini/tree/HEAD)
+- Fixed bug: tasks doesn't works with HTTP server
+- Fixed package incompatibility
 
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.3.0...HEAD)
+## [v0.6.0](https://github.com/lwinterface/panini/tree/v0.6.0) (2021-11-04)
 
-**Implemented enhancements:**
+- Global refactoring
+- Added new interface for pereodic tasks: @app.task(interval=1)
+- Changed `listen_subject_only_if_include` param in App to function `app.add_filters(include, exclude)`
+- Added ability to use all authorization methods from nats.py
+- Added ability to establish connection to multiple NATS brokers
+- Added start message when starting in terminal
 
-- Naming issue in check\_logger - use check\_logger\_connected instead [\#148](https://github.com/lwinterface/panini/issues/148)
-- Cut sync strategy [\#137](https://github.com/lwinterface/panini/issues/137)
+## [v0.5.2](https://github.com/lwinterface/panini/tree/v0.5.2) (2021-08-17)
 
-**Fixed bugs:**
+- Added ability to use any parameters for aiohttp including ssl_context(for HTTPS)
 
-- TestClient problem on @client.listen - do not wait, while publishing response back [\#153](https://github.com/lwinterface/panini/issues/153)
+## [v0.5.0](https://github.com/lwinterface/panini/tree/v0.5.0) (2021-07-21)
 
-**Closed issues:**
+- Implemented AsyncTestClient for better testing experience
+- Added listen_subject_only_if_exclude parameter for excluding unnecessary subjects
 
-- Re-add test for allocation queue group [\#157](https://github.com/lwinterface/panini/issues/157)
-- Fix names for subscribe\_new\_subject \(remove old `aio` interface\) [\#151](https://github.com/lwinterface/panini/issues/151)
-- Remove sync from panini TestClient [\#150](https://github.com/lwinterface/panini/issues/150)
-- Minor issue - remove panini app's arguments 'app\_strategy', 'num\_of\_queues', 'publish\_subjects' [\#149](https://github.com/lwinterface/panini/issues/149)
+## [v0.4.0](https://github.com/lwinterface/panini/tree/v0.4.0) (2021-07-09)
 
-## [v0.3.0](https://github.com/lwinterface/panini/tree/v0.3.0) (2021-04-27)
-
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.2.3...v0.3.0)
-
-**Implemented enhancements:**
-
-- release 0.2.3 [\#139](https://github.com/lwinterface/panini/pull/139) ([artas728](https://github.com/artas728))
-- From json to ujson [\#117](https://github.com/lwinterface/panini/pull/117) ([artas728](https://github.com/artas728))
+- Fixed silent error if response type is list
+- Fixed TestClient
+- Added non-blocking request support for bytes messages
+- Added automatically generated changelog
+- Added pending_bytes_limit parameter to panini App for nats_client
+- Added is_async parameter to subscribe_new_subject for nats_client
+- Added data types for dynamic subscription
+- Added test for long requests
 
-**Fixed bugs:**
+## [v0.3.1](https://github.com/lwinterface/panini/tree/v0.3.1) (2021-05-25)
 
-- Emulator examples does not work: ModuleNotFoundError: No module named 'panini.emulator' [\#145](https://github.com/lwinterface/panini/issues/145)
-- TestClient timed out socket bug [\#142](https://github.com/lwinterface/panini/issues/142)
-- bool fields fix [\#135](https://github.com/lwinterface/panini/pull/135) ([artas728](https://github.com/artas728))
-- Ujson in requirements [\#122](https://github.com/lwinterface/panini/pull/122) ([artas728](https://github.com/artas728))
-- from warn logger to print for experimental warning in emulator [\#116](https://github.com/lwinterface/panini/pull/116) ([artas728](https://github.com/artas728))
+- Fixed bug with no hinting for publish & request functions
+- Removed 'app_strategy' parameter
+- Removed old 'aio' interface for nats_client & managers
+- Added auto unsubscribe to test_client waiting for panini to start
+- Change ci test flow, add test on python v3.9 -major fixes in TestClient, changes in client.wait() function
+- Added non-blocking request support for bytes messages
 
-**Merged pull requests:**
+## [v0.3.0](https://github.com/lwinterface/panini/tree/v0.3.0) (2021-04-27)
 
-- Removed sync app strategy [\#147](https://github.com/lwinterface/panini/pull/147) ([artas728](https://github.com/artas728))
-- Cut sync strategy [\#146](https://github.com/lwinterface/panini/pull/146) ([danylott](https://github.com/danylott))
-- \#142 - added auto-reconnect parameter to TestClient, that will reconn… [\#143](https://github.com/lwinterface/panini/pull/143) ([danylott](https://github.com/danylott))
-- Bug/logger not working until app start [\#136](https://github.com/lwinterface/panini/pull/136) ([danylott](https://github.com/danylott))
-- hotfix for test\_wss - added sleep waiting for websockets [\#132](https://github.com/lwinterface/panini/pull/132) ([danylott](https://github.com/danylott))
-- Circleci project setup [\#129](https://github.com/lwinterface/panini/pull/129) ([bugimprover](https://github.com/bugimprover))
-- \#125 - fixed test\_middleware\_manager - make independent testing middl… [\#128](https://github.com/lwinterface/panini/pull/128) ([danylott](https://github.com/danylott))
-- \#118 - renamed: app.\_client\_id -\> app.client\_id in PrometheusMonitori… [\#121](https://github.com/lwinterface/panini/pull/121) ([danylott](https://github.com/danylott))
-- \#101 - added posibility to pass args and kwargs to TestClient run\_panini [\#112](https://github.com/lwinterface/panini/pull/112) ([danylott](https://github.com/danylott))
-- \#109 - fix requirements prometheus client on develop [\#110](https://github.com/lwinterface/panini/pull/110) ([danylott](https://github.com/danylott))
-- changed ":" to "-" for emulator files [\#108](https://github.com/lwinterface/panini/pull/108) ([oleksii-v](https://github.com/oleksii-v))
+- removed sync app connection strategy
+- removed redis dependency
+- minor fix
 
 ## [v0.2.3](https://github.com/lwinterface/panini/tree/v0.2.3) (2021-04-06)
 
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.2.2...v0.2.3)
-
-**Implemented enhancements:**
-
-- Use ujson instead of json to increase performance [\#114](https://github.com/lwinterface/panini/issues/114)
-- app.logger and get\_logger\(\) is not working untill app.start\(\) [\#113](https://github.com/lwinterface/panini/issues/113)
-- add posibility to pass args and kwargs to TestClient run\_panini [\#101](https://github.com/lwinterface/panini/issues/101)
-- Middleware that count amount of request by status [\#82](https://github.com/lwinterface/panini/issues/82)
-- Use labels in ListenPerformanceMonitoring middleware [\#81](https://github.com/lwinterface/panini/issues/81)
-
-**Fixed bugs:**
-
-- Bool field in validator doesn't work [\#134](https://github.com/lwinterface/panini/issues/134)
-- Panini tests do not pass on Ubuntu with `pytest` command [\#125](https://github.com/lwinterface/panini/issues/125)
-- ModuleNotFoundError: No module named 'ujson' [\#120](https://github.com/lwinterface/panini/issues/120)
-- Error in PrometheusMonitoringMiddleware: AttributeError: 'App' object has no attribute '\_client\_id' [\#118](https://github.com/lwinterface/panini/issues/118)
-- fix requirements prometheus client on develop [\#109](https://github.com/lwinterface/panini/issues/109)
-- Error while cloning panini: error: unable to create file examples/emulator/resources/events.listener.2021-03-19-16:21:13.jsonl: Invalid argument [\#107](https://github.com/lwinterface/panini/issues/107)
-- RuntimeWarning in time sending panini\_event.\*.\*.started with NATSTimeoutMiddleware [\#98](https://github.com/lwinterface/panini/issues/98)
-- Fix app.\_start\_tasks [\#88](https://github.com/lwinterface/panini/issues/88)
-- test\_client sleep\_time - depends on computer power [\#79](https://github.com/lwinterface/panini/issues/79)
-- None does not have SIGKILL [\#28](https://github.com/lwinterface/panini/issues/28)
-
-**Closed issues:**
-
-- Integrate emulation system [\#49](https://github.com/lwinterface/panini/issues/49)
-- Additional\(optional\) datatypes for messages: str, bytes [\#35](https://github.com/lwinterface/panini/issues/35)
-- ./run\_test.sh - modify tests to run with simple pytest command, instead of shell script [\#27](https://github.com/lwinterface/panini/issues/27)
--  Change arguments format in endpoints [\#25](https://github.com/lwinterface/panini/issues/25)
-- Not logic sub title [\#13](https://github.com/lwinterface/panini/issues/13)
-
-**Merged pull requests:**
-
-- Release v0.2.3 [\#140](https://github.com/lwinterface/panini/pull/140) ([artas728](https://github.com/artas728))
+- Added CI/CD checks
+- Moved from json to ujson
+- Fixed logging bugs
+- Fixed bug with emulator for windows
+- Many minor bugs
 
 ## [v0.2.2](https://github.com/lwinterface/panini/tree/v0.2.2) (2021-03-24)
 
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.2.1...v0.2.2)
-
-**Implemented enhancements:**
-
-- Minor fix: change grafana dashboard default rate size from 1m to 15m [\#102](https://github.com/lwinterface/panini/issues/102)
-- Panini dashboard [\#83](https://github.com/lwinterface/panini/issues/83)
-- Release v0.2.2 [\#105](https://github.com/lwinterface/panini/pull/105) ([artas728](https://github.com/artas728))
-- Release v0.2.2 [\#104](https://github.com/lwinterface/panini/pull/104) ([artas728](https://github.com/artas728))
-- 1m default rate size changed to 15m [\#103](https://github.com/lwinterface/panini/pull/103) ([artas728](https://github.com/artas728))
-
-**Fixed bugs:**
-
-- :wrench: app selection fix \#16 [\#96](https://github.com/lwinterface/panini/pull/96) ([artas728](https://github.com/artas728))
-
-**Merged pull requests:**
-
-- Emulator [\#99](https://github.com/lwinterface/panini/pull/99) ([oleksii-v](https://github.com/oleksii-v))
-- Fix test client sleep time [\#97](https://github.com/lwinterface/panini/pull/97) ([danylott](https://github.com/danylott))
-- Panini dashboard [\#95](https://github.com/lwinterface/panini/pull/95) ([artas728](https://github.com/artas728))
-- :chart\_with\_upwards\_trend: added grafana dashboard [\#94](https://github.com/lwinterface/panini/pull/94) ([artas728](https://github.com/artas728))
-- version updated to 0.2.1 [\#93](https://github.com/lwinterface/panini/pull/93) ([artas728](https://github.com/artas728))
-
-## [v0.2.1](https://github.com/lwinterface/panini/tree/v0.2.1) (2021-03-17)
-
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.2.0...v0.2.1)
-
-**Implemented enhancements:**
-
-- Initialize logger only at \_start\(\) [\#67](https://github.com/lwinterface/panini/issues/67)
-
-**Fixed bugs:**
-
-- How to request default logger if there is no access too "app" object? [\#51](https://github.com/lwinterface/panini/issues/51)
-
-**Merged pull requests:**
-
-- Monitoring middleware [\#92](https://github.com/lwinterface/panini/pull/92) ([artas728](https://github.com/artas728))
-- hotfix: send Panini event "started" only for asyncio app\_strategy [\#91](https://github.com/lwinterface/panini/pull/91) ([artas728](https://github.com/artas728))
-- Send message when NATSClient started [\#90](https://github.com/lwinterface/panini/pull/90) ([artas728](https://github.com/artas728))
-- Prometheus monitoring middleware [\#86](https://github.com/lwinterface/panini/pull/86) ([danylott](https://github.com/danylott))
-- \#67 - run logger on app.\_start\(\), added test\_client.stop\(\) method, im… [\#80](https://github.com/lwinterface/panini/pull/80) ([danylott](https://github.com/danylott))
-- added labels to ListenerMonitoring [\#75](https://github.com/lwinterface/panini/pull/75) ([artas728](https://github.com/artas728))
-- For v0.2.0 [\#70](https://github.com/lwinterface/panini/pull/70) ([artas728](https://github.com/artas728))
+- Added Emulator middlewares: ReaderEmulatorMiddleware & WriterEmulatorMiddleware
+- Fixed Test client
+- Minor fix: grafana dashboard default rate size changed from 1m to 15m PrometheusMonitoringMiddleware(ex ListenPerformancePrometheusTracerMiddleware)
 
 ## [v0.2.0](https://github.com/lwinterface/panini/tree/v0.2.0) (2021-03-12)
 
-[Full Changelog](https://github.com/lwinterface/panini/compare/0.1.2...v0.2.0)
-
-**Fixed bugs:**
-
-- ListenPerformancePrometheusTracerMiddleware doesn't work [\#59](https://github.com/lwinterface/panini/issues/59)
-- Middlewares don't work for dynamic listeners [\#58](https://github.com/lwinterface/panini/issues/58)
-- send\_any, send\_publish, send\_request in one middleware [\#41](https://github.com/lwinterface/panini/issues/41)
-- Remove annotations on message for publish and request - because for now we use more than just dict [\#38](https://github.com/lwinterface/panini/issues/38)
-- Middlewares for dynamic listeners and ListenPerformancePrometheusTracerMiddleware minor fix  [\#62](https://github.com/lwinterface/panini/pull/62) ([artas728](https://github.com/artas728))
-
-**Closed issues:**
-
-- Rename ListenPerformancePrometheusTracerMiddleware to something shorter [\#57](https://github.com/lwinterface/panini/issues/57)
-- Change log.warning -\> log.info in simple\_examples, because we use for now log.info as default console format [\#52](https://github.com/lwinterface/panini/issues/52)
-- How to deliver build-in middlewares? [\#50](https://github.com/lwinterface/panini/issues/50)
-- Make TestClient listen interface the same, as in app.listen\(\) [\#30](https://github.com/lwinterface/panini/issues/30)
-
-**Merged pull requests:**
-
-- Added new packages and dependencies [\#73](https://github.com/lwinterface/panini/pull/73) ([artas728](https://github.com/artas728))
-- added new packages and dependencies [\#72](https://github.com/lwinterface/panini/pull/72) ([artas728](https://github.com/artas728))
-- Listen monitoring fix [\#71](https://github.com/lwinterface/panini/pull/71) ([artas728](https://github.com/artas728))
-- Listen monitoring percentile [\#69](https://github.com/lwinterface/panini/pull/69) ([danylott](https://github.com/danylott))
-- quickfix - use Msg in test\_client [\#68](https://github.com/lwinterface/panini/pull/68) ([danylott](https://github.com/danylott))
--  \#30 - client.listen interface now is the same as in nats\_client \(usi… [\#66](https://github.com/lwinterface/panini/pull/66) ([danylott](https://github.com/danylott))
-- \#38 - improved nats\_client interface, removed annotations from message [\#65](https://github.com/lwinterface/panini/pull/65) ([danylott](https://github.com/danylott))
-- get\_logger default parameter and log.warning -\> log.info in examples  [\#64](https://github.com/lwinterface/panini/pull/64) ([danylott](https://github.com/danylott))
-- Built in middlewares NATSTimeout & ListenPerfomanceMonitoring [\#55](https://github.com/lwinterface/panini/pull/55) ([artas728](https://github.com/artas728))
-- Built in middlewares [\#54](https://github.com/lwinterface/panini/pull/54) ([danylott](https://github.com/danylott))
-
-## [0.1.2](https://github.com/lwinterface/panini/tree/0.1.2) (2021-03-10)
-
-[Full Changelog](https://github.com/lwinterface/panini/compare/v0.1.3...0.1.2)
-
-## [v0.1.3](https://github.com/lwinterface/panini/tree/v0.1.3) (2021-03-10)
-
-[Full Changelog](https://github.com/lwinterface/panini/compare/0.1.1...v0.1.3)
-
-**Implemented enhancements:**
-
-- Move validators to pydantic [\#18](https://github.com/lwinterface/panini/issues/18)
-- Test validators [\#11](https://github.com/lwinterface/panini/issues/11)
-
-**Fixed bugs:**
-
-- Test client does not show errors from panini side - only errors, that returns back or logged [\#46](https://github.com/lwinterface/panini/issues/46)
-
-**Closed issues:**
-
-- Test different datatypes functionality [\#48](https://github.com/lwinterface/panini/issues/48)
-- Update in README for middlewares [\#42](https://github.com/lwinterface/panini/issues/42)
-- test wss example [\#23](https://github.com/lwinterface/panini/issues/23)
-- Use Black8 as default project formatter [\#21](https://github.com/lwinterface/panini/issues/21)
-- Default logger level  [\#16](https://github.com/lwinterface/panini/issues/16)
-- Used Python Libraries [\#14](https://github.com/lwinterface/panini/issues/14)
-- Change word "topic" to "subject" [\#12](https://github.com/lwinterface/panini/issues/12)
-- slow customers  [\#4](https://github.com/lwinterface/panini/issues/4)
-- Middleware [\#3](https://github.com/lwinterface/panini/issues/3)
-
-**Merged pull requests:**
-
-- \#46 - added error logging for panini testclient [\#47](https://github.com/lwinterface/panini/pull/47) ([danylott](https://github.com/danylott))
-- :coconut: few words about middlewares [\#45](https://github.com/lwinterface/panini/pull/45) ([artas728](https://github.com/artas728))
-- Middleware integration [\#40](https://github.com/lwinterface/panini/pull/40) ([artas728](https://github.com/artas728))
-- make tests independent - to run it simply using pytest command [\#36](https://github.com/lwinterface/panini/pull/36) ([danylott](https://github.com/danylott))
-- Bump aiohttp from 3.6.3 to 3.7.4 in /examples/dockercompose\_project/microservice1 [\#33](https://github.com/lwinterface/panini/pull/33) ([dependabot[bot]](https://github.com/apps/dependabot))
-- \#21 - applied black formatter, flake8 python PEP8 principles and  [\#32](https://github.com/lwinterface/panini/pull/32) ([danylott](https://github.com/danylott))
-- Test validators wss \#11, \#23 [\#31](https://github.com/lwinterface/panini/pull/31) ([danylott](https://github.com/danylott))
-- middleware\_dict using msg object functionality [\#29](https://github.com/lwinterface/panini/pull/29) ([danylott](https://github.com/danylott))
-- :wrench: readme minor fix [\#24](https://github.com/lwinterface/panini/pull/24) ([artas728](https://github.com/artas728))
-- Added middleware dict feature -  [\#22](https://github.com/lwinterface/panini/pull/22) ([danylott](https://github.com/danylott))
-- Added dynamic subscribe/unsubscribe [\#20](https://github.com/lwinterface/panini/pull/20) ([artas728](https://github.com/artas728))
-- Bump aiohttp from 3.6.3 to 3.7.4 in /requirements [\#15](https://github.com/lwinterface/panini/pull/15) ([dependabot[bot]](https://github.com/apps/dependabot))
-
-## [0.1.1](https://github.com/lwinterface/panini/tree/0.1.1) (2021-02-26)
-
-[Full Changelog](https://github.com/lwinterface/panini/compare/7751489a13af06fba290a64dec0d224e8bc171ab...0.1.1)
-
-**Fixed bugs:**
-
-- Tests on Mac & Windows platform [\#8](https://github.com/lwinterface/panini/issues/8)
-- Logger in separate process error on Mac & Windows [\#6](https://github.com/lwinterface/panini/issues/6)
-- Windows support [\#5](https://github.com/lwinterface/panini/issues/5)
-- having a root logging [\#1](https://github.com/lwinterface/panini/issues/1)
-
-**Closed issues:**
-
-- Default logging in the main process [\#10](https://github.com/lwinterface/panini/issues/10)
-
-**Merged pull requests:**
-
-- \#8 fix tests for support Mac & Windows - fixed using pytest.fixture\(s… [\#9](https://github.com/lwinterface/panini/pull/9) ([danylott](https://github.com/danylott))
-- \#5 \#6 issues handled [\#7](https://github.com/lwinterface/panini/pull/7) ([danylott](https://github.com/danylott))
-- Testclient [\#2](https://github.com/lwinterface/panini/pull/2) ([danylott](https://github.com/danylott))
+- Msg object instead of arguments subject and message in listener's callbacks
+- Addition datatypes supported: bytes, str
+- Added publish/request from another thread
+- Added http-like middlewares
+- Added default middleware that calculates processing time for incoming messages and sends it to pushgateway(prometheus)
+- Added default middleware that sends alert message to some topic if service for NatsTimeoutError
+- Changed default console logging level: WARNING -> INFO
+- Added new examples
 
 
 
-\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
diff --git a/README.md b/README.md
index 2c6017f..25241b4 100644
--- a/README.md
+++ b/README.md
@@ -1,475 +1,283 @@
 # Panini
   
-Panini is a modern framework for fast and straightforward microservices development. Our goal is to create fastapi/aiohttp/flask-like solution but for NATS streaming.
- 
-The framework allows you to work with NATS features and some additional logic using a simple interface:
-*  easy to initialize application
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-        service_name='your-microservice-name',
-        host='127.0.0.1',
-        port=4222,
-)
-```
-*  stream via NATS broker to some subject
-```python
-@app.task()
-async def publish():
-    while True:
-        message = get_some_update()
-        await app.publish(subject='some.subject', message=message)
-```
-*  subscribe to subject
-```python
-@app.listen('some.subject')
-async def subject_for_requests_listener(msg):
-    subject = msg.subject
-    message = msg.data
-    # handle incoming message
-```
-*  request to subject
-```python
-response = await app.request(subject='some.request.subject.123', message={'request':'params'})
-```
-* receive a request from another microservice and return a response like HTTP request-response
-```python
-@app.listen('some.request.subject.123')
-async def request_listener(msg):
-    subject = msg.subject
-    message = msg.data
-    # handle request
-    return {'success': True, 'data': 'request has been processed'}
-```
-* create periodic tasks
-```python
-@app.timer_task(interval=2)
-async def your_periodic_task():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message={'some':'data'})
-```
-* synchronous and asynchronous endpoints
-```python
-@app.timer_task(interval=2)
-def your_periodic_task():
-    for _ in range(10):
-        app.publish_sync(subject='some.publish.subject', message={'some':'data'})
-```
-* accept different datatypes: dict, str, bytes
-```python
-@app.timer_task(interval=2)
-def your_periodic_task():
-    for _ in range(10):
-        app.publish_sync(subject='some.publish.subject', message=b'messageinbytesrequiresminimumoftimetosend', data_type=bytes)
-```
-* create middlewares for NATS messages
-
-```python
-from panini.middleware import Middleware
-
-class MyMiddleware(Middleware):
-
-    async def send_publish(self, subject, message, publish_func, **kwargs):
-        print('do something before publish')
-        await publish_func(subject, message, **kwargs)
-        print('do something after publish')
-
-    async def listen_publish(self, msg, cb):
-        print('do something before listen')
-        await cb(msg)
-        print('do something after listen')
-
-    async def send_request(self, subject, message, request_func, **kwargs):
-        print('do something before send request')
-        result = await request_func(subject, message, **kwargs)
-        print('do something after send request')
-        return result
-
-    async def listen_request(self, msg, cb):
-        print('do something before listen request')
-        result = await cb(msg)
-        print('do something after listen request')
-        return result
-```
-* create HTTP endpoints with [aiohttp](https://github.com/aio-libs/aiohttp) and NATS endpoints all together in one microservice
-```python
-from aiohttp import web
-
-@app.listen('some.publish.subject')
-async def subject_for_requests_listener(msg):
-    handle_incoming_message(msg.subject, msg.data)
-
-@app.http.get('/get')
-async def web_endpoint_listener(request):
-    """
-    Single HTTP endpoint
-    """
-    return web.Response(text="Hello, world")
-
-@app.http.view('/path/to/rest/endpoints')
-class MyView(web.View):
-    """
-    HTTP endpoints for REST schema
-    """
-    async def get(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-
-    async def post(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-
-```
-* built-in traffic balancing between instances of the microservice if you have high loads
-```python
-app = panini_app.App(
-        service_name='async_publish',
-        host='127.0.0.1',
-        allocation_queue_group='group24', 
-        port=4222,
-)
-
-# incoming traffic will be distributed among 
-# all microservices that are in the "group24"
-
-```
-What is NATS? 
-
-It is a high-performance messaging system written in Golang. It is straightforward to use, can run millions of messages per minute through one broker, and easily scales if you need many brokers. Has 48 well-known clients, 11 of which are supported by maintainers, 18 are contributors to the community. Delivery Guarantees, High Availability and Fault Tolerance. Panini based on NATS python client.
- 
-
-What is subject? 
-
-It is just a string of characters that form a name the publisher and subscriber can use to find each other. NATS supports a wild card that dramatically increases the usability of subjects
-
-NATS support subject hierarchies and wildcards
- 
-What is a hierarchy? [here](https://docs.nats.io/nats-concepts/subjects#subject-hierarchies)
+Panini is a python microframework based on the [nats.py](http://nats.py/) library. Its goal is to offer developers an easy way to create NATS microservices with a lower barrier of entry. It provides a specific template for creating microservices, similarly to FastAPI, Aiohttp, or Flask. Like all of the above frameworks, Panini has its design limits and edge cases. In the case that you become restricted by Panini's capabilities, we recommend switching to [nats.py](https://github.com/nats-io/nats.py).
 
-What is a wildcard? [here](https://docs.nats.io/nats-concepts/subjects#subject-hierarchies)
-
- 
-Just try following the examples to see how easy it is.
-
-The project is inspired by faust, the wonderful python project based on Kafka Streams
 
+Panini was inspired by [Faust](https://github.com/robinhood/faust) project. 
 ## Documentation
-is coming...
 
-## Installing
-
-```bash
-pip install panini
-```
+Documentation is [here](https://panini.technology/).
 
-Additional requirements:
-- python >= 3.8.2
+## Supported platforms
 
-## Broker
+Compatible with [Python 3.9](https://docs.python.org/3.9/library/asyncio.html).
 
-Run broker from a directory that include file [docker-compose.yml](https://github.com/lwinterface/panini/blob/master/docker-compose.yml). Command below:
-```bash
-docker-compose up
-```
-
-Stop broker:
-```bash
-docker-compose down
-```
 
-Note, for production we recommend running the broker with dockerized microservices. Example of dockerized panini project [here](https://github.com/lwinterface/panini/examples/dockercompose_project)
 
-## Examples
 
-### Publish
+## A simple listener app example
 
-For streams:
+A minimal app with one stream endpoint, one request endpoint, and one periodic task might look like this:
 
 ```python
-
 from panini import app as panini_app
 
 app = panini_app.App(
-        service_name='async_publish',
+        service_name='listener_app',
         host='127.0.0.1',
         port=4222,
 )
 
-log = app.logger
-
-msg = {'key1':'value1', 'key2':2, 'key3':3.0, 'key4':[1,2,3,4], 'key5':{'1':1, '2':2, '3':3, '4':4, '5':5}, 'key6':{'subkey1':'1', 'subkey2':2, '3':3, '4':4, '5':5}, 'key7':None}
-
-@app.task()
-async def publish():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message=msg)
-        log.info(f'send message {msg}')
-
-
-@app.timer_task(interval=2)
-async def publish_periodically():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message=msg)
-        log.info(f'send message from periodic task {msg}')
-
+@app.listen("some.subject.for.request")
+async def request_listener(msg):
+    """ request endpoint """
+    print(f"request {msg.data} from {msg.subject} has been processed")
+    return {"success": True, "message": "request has been processed"}
 
-@app.listen('some.publish.subject')
-async def receive_messages(msg):
-    log.info(f'got message {msg.data}')
+@app.listen("some.subject.for.stream")
+async def stream_listener(msg):
+    """ stream endpoint """
+    print(f"event {msg.data} from {msg.subject} has been processed")
 
 if __name__ == "__main__":
     app.start()
-
 ```
 
-Let's say name of script above `app.py`. Be sure that broker is running and just execute:
+What's going on here?
 
-```python
-python3 app.py
-```
+1. Imported Panini.
+2. Initialized app. Created an instance of class App from module panini with any microservice name, NATS host, and port.
+3. First <span class="red">`@app.listen`</span> registers the listening subject <span class="red">`"some.subject.for.request"`</span> with <span class="red">`request_listener`</span>. Every time this app receives a request addressed to <span class="red">`"some.subject.for.request"`</span>, the function <span class="red">`request_listener`</span> is called to process it, then it sends a return response back to an addressee.
+4. Secondly <span class="red">`@app.listen`</span> register the listening subject <span class="red">`"some.subject.for.stream"`</span> with <span class="red">`stream_listener`</span>. Same as with <span class="red">`request_listener`</span> but without sending the result back.
+5. <span class="red">`app.start()`</span> runs an app. No code under this command will ever be called.
 
-It's all! Microservice launched
+Save the above code to file *listener*_*app.py.*
 
-### Request
 
-Classical request-response:
+<aside>
+💡 The current function expects only JSON formattable returns, dict or list. However, you can also specify it as string or bytes. More details about this in Datatypes section._
 
-```python
-from panini import app as panini_app
+</aside>
 
-app = panini_app.App(
-    service_name='async_request',
-    host='127.0.0.1',
-    port=4222,
-)
-
-log = app.logger
 
-msg = {'key1': 'value1', 'key2': 2, 'key3': 3.0, 'key4': [1, 2, 3, 4], 'key5': {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5},
-       'key6': {'subkey1': '1', 'subkey2': 2, '3': 3, '4': 4, '5': 5}, 'key7': None}
+Make sure that you have all prerequisites from Install. Open the terminal to run the app:
 
-@app.task()
-async def request():
-    for _ in range(10):
-        result = await app.request(subject='some.request.subject.123', message=msg)
-        log.info(f'response: {result}')
-
-@app.listen('some.request.subject.123')
-async def request_listener(msg):
-    log.info('request has been processed')
-    return {'success': True, 'data': f'request from {msg.subject} has been processed'}
+```python
+> python3 listener_app.py
+======================================================================================
+Panini service connected to NATS..
+id: 3
+name: listener_app__non_docker_env_270377__75017
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+```
 
+That's it. Now let's create something that will generate messages.
 
-if __name__ == "__main__":
-    app.start()
-```
+## A simple app example that generates messages
 
-### Request with response to another subject
+Our goal here is to trigger endpoints from listener app above: 
 
-A response of request is sent to the third subject. This method can significantly increase the throughput in comparison to classical request-response model
+- <span class="purple">"some.subject.for.request"</span>  - request something, receive response
+- <span class="purple">"some.subject.for.stream"</span> - send some event without waiting for response
 
 ```python
-
 from panini import app as panini_app
 
 app = panini_app.App(
-    service_name='async_reply_to',
-    host='127.0.0.1',
-    port=4222,
+        service_name='sender_app',
+        host='127.0.0.1',
+        port=4222,
 )
 
-log = app.logger
-
-msg = {'key1': 'value1', 'key2': 2, 'key3': 3.0, 'key4': [1, 2, 3, 4], 'key5': {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5},
-       'key6': {'subkey1': '1', 'subkey2': 2, '3': 3, '4': 4, '5': 5}, 'key7': None}
-
-@app.task()
-async def request_to_another_subject():
-    for _ in range(10):
-        await app.publish(subject='some.subject.for.request.with.response.to.another.subject',
-                          message=msg,
-                          reply_to='reply.to.subject')
-        log.info('sent request')
-
-@app.listen('some.subject.for.request.with.response.to.another.subject')
-async def request_listener(msg):
-    log.info('request has been processed')
-    return {'success': True, 'data': f'request from {msg.subject} has been processed'}
-
-@app.listen('reply.to.subject')
-async def another_subject_listener(msg):
-    log.info(f'received response: {msg.subject} {msg.data}')
-
+@app.task(interval=1)
+async def request_periodically():
+		message = {"data":"request1234567890"}
+    response = await app.request(
+        subject="some.subject.for.request", 
+        message=message,
+    )
+    print(response)
+		
+
+@app.task(interval=1)
+async def publish_periodically():
+		message = {"data":"event1234567890"}
+    await app.publish(
+        subject="some.subject.for.stream", 
+        message=message,
+    )
 
 if __name__ == "__main__":
     app.start()
-
 ```
 
-### Validators
+What's new here:
 
-Validator allows you to validate incoming messages:
+1. First, <span class="red">`@app.task`</span> registers function <span class="red">`request_periodically`</span> to call it periodically at given interval, each 1 second in the example.
+2. Function <span class="red">`app.request`</span> sends requests, asynchronously waits for a response.
+3. The second <span class="red">`@app.task`</span> does the same as the first one but for publishing.
+4. Function <span class="red">`app.publish`</span> sends a message like a request but without expecting any response. Fire and forget.
 
-```python
+Save the code to new file *sender_app.py.*
 
-from panini import app as panini_app
-from panini.validator import Validator, Field
-
-app = panini_app.App(
-    service_name='validators',
-    host='127.0.0.1',
-    port=4222,
-)
-
-log = app.logger
+Make sure that *listener_app.py* keeps running, then open a new terminal session to run the sender app:
 
+```python
+> python3 sender_app.py
+======================================================================================
+Panini service connected to NATS..
+id: 3
+name: sender_app__non_docker_env_270377__75017
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+```
 
-class SubTestValidator(Validator):
-    subkey1 = Field(type=str)
-    subkey2 = Field(type=int)
+Note that in the terminal session where you run *listener_app.py* you should see received requests and events:
 
+```python
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+```
 
-class TestValidator(Validator):
-    key1 = Field(type=str)
-    key2 = Field(type=int)
-    key3 = Field(type=float)
-    key4 = Field(type=list)
-    key5 = Field(type=dict)
-    key6 = Field(type=SubTestValidator)
-    key7 = Field(type=int, null=True)
-    key8 = Field(type=int, null=True, default=None)
+## More possibilities
 
+In the first example, we created an application that listens for messages, in the second example, an application that sends messages. Panini allows you to freely combine sending and receiving messages in one application. 
 
-msg = {'key1': 'value1', 'key2': 2, 'key3': 3.0, 'key4': [1, 2, 3, 4], 'key5': {'1': 1, '2': 2, '3': 3, '4': 4, '5': 5},
-       'key6': {'subkey1': '1', 'subkey2': 2, '3': 3, '4': 4, '5': 5}, 'key7': None}
+Let's check out what else you can do with Panini using a minimal interface:
 
+- One-time tasks on start. Similar to the above periodic task but without <span class="red">`interval`</span> argument
 
+```python
 @app.task()
 async def publish():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message=msg)
+    while True:
+        message = get_some_update()
+        await app.publish(subject='some.subject', message=message)
+```
 
+- Synchronous endpoints
 
-@app.timer_task(interval=2)
-async def publish_periodically():
+```python
+@app.task(interval=2)
+def your_periodic_task():
     for _ in range(10):
-        await app.publish(subject='some.publish.subject', message=msg)
-
-
-@app.listen('some.publish.subject', validator=TestValidator)
-async def subject_for_requests_listener(msg):
-    log.info(f'got message {msg.data}')
-
-
-if __name__ == "__main__":
-    app.start()
-
+        app.publish_sync(
+            subject='some.publish.subject', 
+            message={'some':'data'}
+        )
 ```
 
-### HTTP server
-
-You must specify web_server=True to activate the web server. [Aiohttp](https://docs.python.org/3/library/asyncio.html) is used as a web server. Accordingly, you can use their syntax.
-Also, you can specify web
+- Accept different datatypes: dict, str, bytes
 
 ```python
-
-from aiohttp import web
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name='async_web_server',
-    host='127.0.0.1',
-    port=4222,
-    web_server=True,
-    web_host='127.0.0.1',
-    web_port=8999,
-)
-
-log = app.logger
-
-@app.http.get('/get')
-async def web_endpoint_listener(request):
-    """
-    Single HTTP endpoint
-    """
-    return web.Response(text="Hello, world")
-
-@app.http.view('/path/to/rest/endpoints')
-class MyView(web.View):
-    """
-    HTTP endpoints for REST schema
-    """
-    async def get(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-
-    async def post(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-
-
-if __name__ == "__main__":
-    app.start()
-
-
+@app.timer_task(interval=2)
+def your_periodic_task():
+    for _ in range(10):
+        app.publish_sync(
+            subject='some.publish.subject', 
+            message=b'messageinbytestosend', 
+            data_type=bytes
+        )
 ```
 
-## Logging
-
-Panini creates a logfile folder in the project directory and stores all logs there. There are several ways to store your own logs there.
-
-Logging from app object:
+- Create middlewares for NATS messages
 
 ```python
-from panini import app as panini_app
-from panini.utils.logger import get_logger
-
-app = panini_app.App(  # create app
-    service_name='ms_template_sync_by_lib',
-    host='127.0.0.1',
-    port=4222,
-)
-
-log = app.logger  # create log handler
-log = get_logger('ms_template_sync_by_lib')  # does exactly the same thing
-
-log.info("some log")  # write log
-log.warning("some warn log")
-log.error("some error log")
-log.exception("some exception log with automatic traceback logging")
-
-```
+from panini.middleware import Middleware
 
-Separated (after setting at the startup - you can get any registered logger with a get_logger function):
+class MyMiddleware(Middleware):
 
-```python
-from panini.utils.logger import get_logger
+    async def send_publish(self, subject, message, publish_func, **kwargs):
+        print('do something before publish')
+        await publish_func(subject, message, **kwargs)
+        print('do something after publish')
 
-log = get_logger('some_logger_name')
+    async def listen_publish(self, msg, cb):
+        print('do something before listen')
+        await cb(msg)
+        print('do something after listen')
 
-log.warning("some log")  # write log
+    async def send_request(self, subject, message, request_func, **kwargs):
+        print('do something before send request')
+        result = await request_func(subject, message, **kwargs)
+        print('do something after send request')
+        return result
 
+    async def listen_request(self, msg, cb):
+        print('do something before listen request')
+        result = await cb(msg)
+        print('do something after listen request')
+        return result
 ```
 
-Panini uses logging in main process by default, but you can change it to speed-up application on the app creation:
+- Create HTTP endpoints with [Aiohttp](https://github.com/aio-libs/aiohttp) and NATS endpoints all together in one microservice
+    
+    ```python
+    from aiohttp import web
+    
+    @app.listen('some.publish.subject')
+    async def subject_for_requests_listener(msg):
+        handle_incoming_message(msg.subject, msg.data)
+    
+    @app.http.get('/get')
+    async def web_endpoint_listener(request):
+        """
+        Single HTTP endpoint
+        """
+        return web.Response(text="Hello, world")
+    
+    @app.http.view('/path/to/rest/endpoints')
+    class MyView(web.View):
+        """
+        HTTP endpoints for REST schema
+        """
+        async def get(self):
+            request = self.request
+            return web.Response(text="Hello, REST world")
+    
+        async def post(self):
+            request = self.request
+            return web.Response(text="Hello, REST world")
+    ```
+    
+- Built-in traffic balancing between instances of the microservice if you have high loads
 
 ```python
-from panini import app as panini_app
-
 app = panini_app.App(
-    service_name='ms_template_sync_by_lib',
-    host='127.0.0.1',
-    port=4222,
-    logger_in_separate_process=True,  # specify this option for logging in separate process
+        service_name='async_publish',
+        host='127.0.0.1',
+        allocation_queue_group='group24', 
+        port=4222,
 )
 
+# incoming traffic will be distributed among 
+# all microservices that are in the "group24"
 ```
 
-Panini let you to choose between default (recommended by developers) and custom logger configurations. If you want to
-use custom logging config - just create `config/log_config.json` file with custom logger configuration at the app root.
-Panini will automatically detect and set it. After that you can get your logger with `get_logger` function.
-
+Need more examples? Check [here](https://github.com/lwinterface/panini/tree/master/examples).
 ## Testing
 
 We use [pytest](https://docs.pytest.org/en/stable/) for testing
diff --git a/docs/Application_monitoring.md b/docs/Application_monitoring.md
new file mode 100644
index 0000000..ab8d9d7
--- /dev/null
+++ b/docs/Application_monitoring.md
@@ -0,0 +1,48 @@
+### Overview
+
+With microservices it can be hard to get a full image of what is going on. We decided to implement the built-in possibility to monitor performance in order to reveal problems or bugs in the system faster and simpler.
+
+We are using:
+
+- [**Prometheus**](https://prometheus.io/) time-series database & monitoring solution.
+- [**Grafana**](https://grafana.com/) for user-friendly dashboards.
+- Panini built-in [**middleware**](/Middlewares) [PrometheusMonitoringMiddleware](https://github.com/lwinterface/panini/blob/master/panini/middleware/prometheus_monitoring.py)
+
+The main idea of **PrometheusMonitoringMiddleware** is to collect important metrics, such as:
+
+- Message **rate** by subject
+- Request **status** (success or failure)
+- Request **latency**
+
+After collecting, it periodically sends these metrics to [Prometheus Gateway](https://prometheus.io/docs/instrumenting/pushing/) that reflected directly in your Grafana dashboard*.*
+
+### Initializing the monitoring
+
+Suppose that we already have **Prometheus** & **Grafana** up and running.
+
+After that, we need to add **PrometheusMonitoringMiddleware** to the app:
+
+```python
+from panini.middleware.prometheus_middleware import PrometheusMonitoringMiddleware
+
+app.add_middleware(PrometheusMonitoringMiddleware)
+# you could also specify some custom parameters (see details in middleware constructor):
+# app.add_middleware(PrometheusMonitoringMiddleware, PROMETHEUS_GATEWAY, frequency=30)
+```
+
+**PrometheusMonitoringMiddleware** parameters:
+
+- pushgateway_url: str = "[localhost:9091](http://localhost:9091)" - url to Prometheus PushGateway
+- app = None - panini App
+
+After this, application activity will be stored in Prometheus and demonstrated in the Grafana dashboard.
+
+### Using Grafana & Prometheus
+
+Below is our [dashboard](https://github.com/lwinterface/panini/tree/master/grafana_dashboard) that you can get here:
+
+![https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fb1765716-a820-4878-8112-fa05fb301aa2%2FScreenshot_2021-04-03_at_18.50.32.png?table=block&id=5b003b2e-9a68-41ad-ad49-ce2e2035d60f&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=2000&userId=&cache=v2](https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fb1765716-a820-4878-8112-fa05fb301aa2%2FScreenshot_2021-04-03_at_18.50.32.png?table=block&id=5b003b2e-9a68-41ad-ad49-ce2e2035d60f&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=2000&userId=&cache=v2)
+
+You can use our template or dig into Grafana for a more custom approach.
+
+How to set up Grafana & Prometheus <here>
\ No newline at end of file
diff --git a/docs/Authentication.md b/docs/Authentication.md
new file mode 100644
index 0000000..6a07086
--- /dev/null
+++ b/docs/Authentication.md
@@ -0,0 +1,78 @@
+Authentication is essential for any system, especially microservice architectures. Modern design approaches tend to isolate and encapsulate microservices. For the NATS world, this becomes especially critical with the release of one of the key features of NATS 2.0 - decentralized security. This feature allows creating access groups at NATS client level and at subject level. In other words, it "containerizes" a network, which opens up new possibilities for engineers. 
+
+NATS offers several authentication strategies, we suggest you explore them here: [https://docs.nats.io/developing-with-nats/security](https://docs.nats.io/developing-with-nats/security)
+
+Panini does not interact with the authentication process. In fact, it just gives the interface of [nats.py](http://nats.py/) to establish a connection with authentication. NATS supports user-password, token, JWT, TLS, and NKeys authentication.
+
+Let's make an example with User-password. First, we need to run NATS broker with user-password access:
+
+```python
+nats-server --user john --pass johnpassword
+```
+
+Then let's write a microservice that connects to NATS but without the user and the password:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="some_microservice",
+    host="127.0.0.1",
+    port=4222,
+)
+
+if __name__ == "__main__":
+    app.start()
+```
+
+Let's save it to a file named *[app.py](http://app.py)* and run it:
+
+```python
+> python3 app.py
+2021-11-04 14:58:07,163 nats.aio.client ERROR    nats: encountered error
+Traceback (most recent call last):
+  File "*/*/*", line 318, in connect
+    await self._process_connect_init()
+  File "*/*/*", line 1673, in _process_connect_init
+    raise NatsError("nats: " + err_msg.rstrip('\r\n'))
+nats.aio.errors.NatsError: nats: 'Authorization Violation'
+```
+
+As you can see, an 'Authorization Violation' raised. There is no way to establish a connection with the NATS broker without credentials. Let's add the username and the password to our app:
+
+```python
+from panini import app as panini_app
+
+auth = {
+    "user": "john",
+    "password": "jpassword"
+}
+
+app = panini_app.App(
+    service_name="some_microservice",
+    host="127.0.0.1",
+    port=4222,
+    auth=auth
+)
+
+if __name__ == "__main__":
+    app.start()
+```
+
+Let's run it again:
+
+```python
+> python3 app.py
+======================================================================================
+Panini service connected to NATS..
+id: 3
+name: some_microservice__non_docker_env_270377__75017
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+```
+
+It seems we have connected to the NATS broker! 
+
+You can check out more on authentication in [nats.py specs](https://github.com/nats-io/nats.py/blob/main/readme.md) or [NATS docs](https://docs.nats.io/developing-with-nats/security).
\ No newline at end of file
diff --git a/docs/Balancer.md b/docs/Balancer.md
index c55b4b9..ef403b8 100644
--- a/docs/Balancer.md
+++ b/docs/Balancer.md
@@ -1,21 +1,23 @@
-If you would create two microservices that subscribed on subject `some.data.stream.one`, then both will receive all messages
-
-NATS allows easily unite instances of microservice to groups where incoming traffic will equally allocated among microservices of group:
-
-![https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F43c9351e-31e0-477f-b31b-baeab4dba35a%2Fpanini_balancer_(1).png?table=block&id=ef5eb0f8-52ea-4466-b1c4-f076672f4050&width=1460&userId=&cache=v2](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F43c9351e-31e0-477f-b31b-baeab4dba35a%2Fpanini_balancer_(1).png?table=block&id=ef5eb0f8-52ea-4466-b1c4-f076672f4050&width=1460&userId=&cache=v2)
-
-Below is code example:
-
-```python
-
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="async_publish",
-    host="127.0.0.1",
-    port=4222,
-		allocation_queue_group='group1'
-)
-```
-
-That's it. You only need to specify the group when creating panini app
\ No newline at end of file
+Sometimes there can be too much traffic and it is necessary to scale services horizontally, i. e. create several instances of a microservice in order to distribute the incoming traffic between them. This section will describe how to work with this issue.
+
+Let's say you created two instances of a microservice that are subscribed on the subject <span class="red">`some.data.stream.one`</span>, then both will receive all messages. It's the default behaviour of NATS microservices. NATS allows for easily uniting instances of a microservice to a group where incoming traffic will be equally allocated among instances automatically:
+
+![https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F43c9351e-31e0-477f-b31b-baeab4dba35a%2Fpanini_balancer_(1).png?table=block&id=6e784580-726a-4500-b4e0-a30c70104a43&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=1460&userId=&cache=v2](https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F43c9351e-31e0-477f-b31b-baeab4dba35a%2Fpanini_balancer_(1).png?table=block&id=6e784580-726a-4500-b4e0-a30c70104a43&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=1460&userId=&cache=v2)
+
+How to program traffic distribution with Panini:
+
+```python
+
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="async_publish",
+    host="127.0.0.1",
+    port=4222,
+		allocation_queue_group='unit1'
+)
+```
+
+When the class App is initialized, it takes <span class="red">`allocation_queue_group`</span> as an additional argument. Then all microservices in this group <span class="red">`unit1`</span> will distribute the incoming traffic among the group members.
+
+That's it. You only need to specify the group when creating a Panini app.
\ No newline at end of file
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 5ddad42..548c36f 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -1,1 +1,77 @@
-# Changelog
\ No newline at end of file
+# Changelog
+## [v0.6.2](https://github.com/lwinterface/panini/tree/v0.6.2) (2021-11-11)
+
+- Fixed bug: tasks doesn't works with HTTP server
+- Fixed package incompatibility
+
+## [v0.6.0](https://github.com/lwinterface/panini/tree/v0.6.0) (2021-11-04)
+
+- Global refactoring
+- Added new interface for pereodic tasks: <span class="red">`@app.task(interval=1)`</span>
+- Changed <span class="red">`listen_subject_only_if_include`</span> param in App to function <span class="red">`app.add_filters(include, exclude)`</span>
+- Added ability to use all authorization methods from nats.py
+- Added ability to establish connection to multiple NATS brokers
+- Added start message when starting in terminal
+
+## [v0.5.2](https://github.com/lwinterface/panini/tree/v0.5.2) (2021-08-17)
+
+- Added ability to use any parameters for aiohttp including ssl_context(for HTTPS)
+
+## [v0.5.0](https://github.com/lwinterface/panini/tree/v0.5.0) (2021-07-21)
+
+- Implemented AsyncTestClient for better testing experience
+- Added listen_subject_only_if_exclude parameter for excluding unnecessary subjects
+
+## [v0.4.0](https://github.com/lwinterface/panini/tree/v0.4.0) (2021-07-09)
+
+- Fixed silent error if response type is list
+- Fixed TestClient
+- Added non-blocking request support for bytes messages
+- Added automatically generated changelog
+- Added pending_bytes_limit parameter to panini App for nats_client
+- Added is_async parameter to subscribe_new_subject for nats_client
+- Added data types for dynamic subscription
+- Added test for long requests
+
+## [v0.3.1](https://github.com/lwinterface/panini/tree/v0.3.1) (2021-05-25)
+
+- Fixed bug with no hinting for publish & request functions
+- Removed 'app_strategy' parameter
+- Removed old 'aio' interface for nats_client & managers
+- Added auto unsubscribe to test_client waiting for panini to start
+- Change ci test flow, add test on python v3.9-major fixes in TestClient, changes in client.wait() function
+- Added non-blocking request support for bytes messages
+
+## [v0.3.0](https://github.com/lwinterface/panini/tree/v0.3.0) (2021-04-27)
+
+- removed sync app connection strategy
+- removed redis dependency
+- minor fix
+
+## [v0.2.3](https://github.com/lwinterface/panini/tree/v0.2.3) (2021-04-06)
+
+- Added CI/CD checks
+- Moved from json to ujson
+- Fixed logging bugs
+- Fixed bug with emulator for windows
+- Many minor bugs
+
+## [v0.2.2](https://github.com/lwinterface/panini/tree/v0.2.2) (2021-03-24)
+
+- Added Emulator middlewares: ReaderEmulatorMiddleware & WriterEmulatorMiddleware
+- Fixed Test client
+- Minor fix: grafana dashboard default rate size changed from 1m to 15m PrometheusMonitoringMiddleware(ex ListenPerformancePrometheusTracerMiddleware)
+
+## [v0.2.0](https://github.com/lwinterface/panini/tree/v0.2.0) (2021-03-12)
+
+- Msg object instead of arguments subject and message in listener's callbacks
+- Addition datatypes supported: bytes, str
+- Added publish/request from another thread
+- Added http-like middlewares
+- Added default middleware that calculates processing time for incoming messages and sends it to pushgateway(prometheus)
+- Added default middleware that sends alert message to some topic if service for NatsTimeoutError
+- Changed default console logging level: WARNING -> INFO
+- Added new examples
+
+
+
diff --git a/docs/Data_Validation.md b/docs/Data_Validation.md
new file mode 100644
index 0000000..e6ef23a
--- /dev/null
+++ b/docs/Data_Validation.md
@@ -0,0 +1,148 @@
+When many microservices communicate with each other, an essential consideration for reliability is ensuring that NATS client properly validates incoming messages. Engineers want to make sure that any data that enters an application is valid and secure.
+
+Panini allows for checking all fields and data types of values for incoming JSON messages. Validator is very similar to Serializer in Django or Dataclasses. 
+
+Example of validator:
+
+```python
+class CoctailValidator(Validator):
+    id_ = Field(type=int)
+    name = Field(type=str)
+    price = Field(type=float)
+    ingredients = Field(type=dict)
+    history_of_orders = Field(type=list)
+```
+
+Validator can accept JSON data types or another validator if you need to validate data of a nested dictionary. 
+
+Let's say we want to send a message by <span class="red">`microservice_1`</span> ten times per second, the message:
+
+```python
+{
+    "key1": "value1",
+    "key2": 2,
+    "key3": 3.02444441234,
+    "key4": [1, 2, 3, 4],
+    "key5": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
+    "key6": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
+    "key7": None,
+}
+```
+
+Then receive it by <span class="red">`microservice_2`</span> but validate that all message fields and value types are correct. 
+
+<span class="red">`microservice_1`</span>:
+
+```python
+import json
+from panini import  app as panini_app
+
+app = panini_app.App(
+        service_name='microservice_1',
+        host='127.0.0.1',
+        port=4222,
+)
+
+message = {
+    "key1": "value1",
+    "key2": 2,
+    "key3": 3.024444412342342342,
+    "key4": [1, 2, 3, 4],
+    "key5": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
+    "key6": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
+    "key7": None,
+}
+
+@app.task(interval=1)
+async def publish_periodically():
+    for _ in range(10):
+        await app.publish(
+            subject="some.publish.subject", message=message)
+        )
+
+if __name__ == "__main__":
+    app.start()
+```
+
+<span class="red">`microservice_2`</span>:
+
+```python
+import json
+from panini import  app as panini_app
+
+app = panini_app.App(
+        service_name='microservice_2',
+        host='127.0.0.1',
+        port=4222,
+)
+
+class SubTestValidator(Validator):
+    subkey1 = Field(type=str)
+    subkey2 = Field(type=int)
+
+class TestValidator(Validator):
+    key1 = Field(type=str)
+    key2 = Field(type=int)
+    key3 = Field(type=float)
+    key4 = Field(type=list)
+    key5 = Field(type=dict)
+    key6 = Field(type=SubTestValidator)
+    key7 = Field(type=int, null=True)
+    key8 = Field(type=int, null=True, default=None)
+
+@app.listen("some.publish.subject", validator=TestValidator)
+async def requests_listener(msg):
+    log.info(f"got message {msg.data}")
+    await asyncio.sleep(1)
+
+if __name__ == "__main__":
+    app.start()
+```
+
+The validator model has to inherit Validator class. Acceptable python datatypes: 
+
+- str
+- int
+- float
+- list
+- dict
+- bool
+- None
+- another validation model(SubTestValidator in the example)
+
+Each field optionally allows: 
+
+- add default key/value if they are absent in incoming message
+- allows None(null) type
+- allows many nested objects in list, for example message:
+    
+    ```python
+    {
+        "key1": [
+                   {"subkey1": "asdf1", "subkey2": 342},
+                   {"subkey1": "sdf1", "subkey2": 2},
+                   {"subkey1": "sdfsd1", "subkey2": 2}
+                ]
+    }
+    ```
+    
+    Validators:
+    
+    ```python
+    class SubTestValidator(Validator):
+        subkey1 = Field(type=str)
+        subkey2 = Field(type=int)
+    
+    class TestValidator(Validator):
+        key1 = Field(type=SubTestValidator, many=True) 
+    ```
+    
+
+If an incoming message doesn't pass validation, Panini will raise <span class="red">`ValidationError`</span>.
+
+We consider the current data validation system as underdeveloped. If you feel a lack of features, you can always consider external solutions for this:
+
+- [Dataclasses](https://docs.python.org/3/library/dataclasses.html)
+- [Cerberus](https://docs.python-cerberus.org/en/stable/usage.html#basic-usage)
+- [Colander](https://docs.pylonsproject.org/projects/colander/en/latest/)
+- [Jsonschema](https://python-jsonschema.readthedocs.io/en/latest/)
\ No newline at end of file
diff --git a/docs/Datatypes.md b/docs/Datatypes.md
index e27dc60..3429452 100644
--- a/docs/Datatypes.md
+++ b/docs/Datatypes.md
@@ -1,52 +1,107 @@
-Panini supports 3 datatypes:
-
-- dict
-- str
-- bytes
-
-For example:
-
-```python
-import json
-from panini import  app as panini_app
-
-app = panini_app.App(
-        service_name='quickstart-app',
-        host='127.0.0.1',
-        port=4222,
-)
-
-message = {
-    "key4": "value1",
-    "key7": 2,
-    "key3": 3.024444412342342342,
-    "key1": [1, 2, 3, 4],
-    "key6": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
-    "key5": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
-    "key2": None,
-}
-
-@app.timer_task(interval=1)
-async def publish_periodically():
-    for _ in range(10):
-        await app.publish(
-            subject="some.publish.subject", message=json.dumps(message, sort_keys=True), data_type=str
-        )
-
-@app.listen("some.publish.subject", data_type=str)
-async def receive_messages(msg):
-    log.info(f"got subject {msg.subject}")
-    log.info(f"got message {msg.data}")
-
-@app.listen("some.publish.subject", data_type=bytes)
-async def receive_messages(msg):
-    log.info(f"got subject {msg.subject}")
-    log.info(f"got message {msg.data}")
-
-if __name__ == "__main__":
-    app.start()
-```
-
-This feature allows to use custom `json.dumps` or non-json messages
-
-#TODO example with video/voice stream to show bytes usage
\ No newline at end of file
+### Basics
+
+Every project using a microservice architecture is unique and has different requirements for the message format of communication between microservices. For example, for systems in the e-commerce or fintech industry, JSON format might be appropriate, but if you need to make an audio stream, then you may prefer to work with raw bytes. Panini works with 3 data types of messages:
+
+- JSON - default datatype
+- Bytes
+- String
+
+To set a specific datatype, you need to set it as argument <span class="red">`data_type`</span> when sending messages:
+
+```python
+await app.publish(
+          subject="some.data.in.bytes",
+          message=b'23oesj2o4jrs93oijr9s3oijr02zoje01p92ek012sx01p2ke01pjz',
+          data_type=bytes,
+      )
+```
+
+or listening for messages:
+
+```python
+@app.listen("some.data.in.bytes", data_type=bytes)
+async def receive_bytes(msg):
+    log.info(f"got subject {msg.subject}")
+    log.info(f"got message {msg.data}")
+```
+
+If an incoming message to a microservice requires an answer, Panini expects dict as output type. For example:
+
+```python
+@app.listen("subject.for.request.in.bytes", data_type=bytes)
+async def receive_bytes(msg):
+    data = msg.data
+	  result = some_process_of_request(data)
+    validate_result_is_jsonfyible_dict(result)  # the result should be returned as jsonfyible dict
+    return result
+```
+
+The result should be returned as bytes, otherwise, Panini will raise an exception.
+
+### One more example
+
+JSON type transforms to python's dict inside an endpoint's function. In this case, all the elements of your dict should be capable of jsonfying in order to send a response.
+
+Let's take a look at an example that includes a task that sends a message and 3 listeners that receive this message as different datatypes:
+
+```python
+import json
+from panini import  app as panini_app
+
+app = panini_app.App(
+        service_name='quickstart-app',
+        host='127.0.0.1',
+        port=4222,
+)
+
+message = {
+    "key4": "value1",
+    "key7": 2,
+    "key3": 3.024444412342342342,
+    "key1": [1, 2, 3, 4],
+    "key6": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
+    "key5": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
+    "key2": None,
+}
+
+@app.task(interval=1)
+async def publish_string():
+    some_string = json.dumps(message, sort_keys=True)
+    for _ in range(10):
+        await app.publish(
+            subject="some.publish.subject", 
+            message=some_string, 
+            data_type=str,
+        )
+
+@app.listen("some.publish.subject")
+async def receive_dict(msg):
+    log.info(f"got subject {msg.subject}")
+    log.info(f"got message {msg.data} type: {type(msg.data)}")
+
+@app.listen("some.publish.subject", data_type=str)
+async def receive_string(msg):
+    log.info(f"got subject {msg.subject}")
+    log.info(f"got message {msg.data} type: {type(msg.data)}")
+
+@app.listen("some.publish.subject", data_type=bytes)
+async def receive_bytes(msg):
+    log.info(f"got subject {msg.subject}")
+    log.info(f"got message {msg.data} type: {type(msg.data)}")
+
+if __name__ == "__main__":
+    app.start()
+```
+
+What is going on here:
+
+1. The message template is a dict that can be transformed into JSON
+2. <span class="red">`@app.task`</span> publishes data in string format to subject <span class="red">`"some.publish.subject"`</span>(jsonifyed data is a string)
+3. The first <span class="red">`@app.listen`</span> receives this data as default datatype - transforms it to dict
+4. The second <span class="red">`@app.listen`</span> receives the same data as a string
+5. The third <span class="red">`@app.listen`</span> receives the same data as bytes
+<div class="attention">
+<p class="attention__emoji-icon">💡</p><p> All these datatypes are just representations at the Panini level. Eventually, Panini transforms data into bytes before sending it to NATS for any datatype. When another microservice receives this message, it also receives bytes then transforms it into the requested datatype in the endpoint function.</p>
+</div>
+
+String type is string only inside the endpoint's function. Eventually, a microservice sends bytes anyway.
\ No newline at end of file
diff --git a/docs/Deployment.md b/docs/Deployment.md
index 7a9e3e0..93b9924 100644
--- a/docs/Deployment.md
+++ b/docs/Deployment.md
@@ -1,1 +1,3 @@
-# Deployment
\ No newline at end of file
+<div class="attention">
+<p class="attention__emoji-icon">🛠</p><p> This page is under construction</p>
+</div>
\ No newline at end of file
diff --git a/docs/Distributed_tracing.md b/docs/Distributed_tracing.md
index e711eb5..4394652 100644
--- a/docs/Distributed_tracing.md
+++ b/docs/Distributed_tracing.md
@@ -1,1 +1,8 @@
-# Distributed tracing
\ No newline at end of file
+The tracing feature will be released as soon as the [nats.py](http://nats.py/) on which Panini is based releases the ability to use headers in NATS messages. Panini will provide tools for Opentracing:
+
+- middleware for automatic tracing
+- an interface to manage tracing manually.
+
+<div class="attention">
+<p class="attention__emoji-icon">🛠</p><p> This page is under construction</p>
+</div>
\ No newline at end of file
diff --git a/docs/Emulator.md b/docs/Emulator.md
index 778ebe3..7935731 100644
--- a/docs/Emulator.md
+++ b/docs/Emulator.md
@@ -1,1 +1,4 @@
-# Emulator - "time machine" debug
\ No newline at end of file
+This feature is currently being tested. The main idea is very simple: If the service breaks down, the developer can “roll back time” by N milliseconds and reproduce all messages that come to the microservice in debug mode.
+<div class="attention">
+<p class="attention__emoji-icon">🛠</p><p> This page is under construction</p>
+</div>
\ No newline at end of file
diff --git a/docs/Examples.md b/docs/Examples.md
index b2faa35..6aeda9b 100644
--- a/docs/Examples.md
+++ b/docs/Examples.md
@@ -1,5 +1,5 @@
-[Simple examples](https://www.notion.so/Simple-examples-cec8f09d37aa45b7bba76fd78077e2b9)
-
-[Panini+PostgreSQL](https://www.notion.so/Panini-PostgreSQL-6fc2920642774eceb97f4c7e489affe1)
-
+[Simple examples](https://www.notion.so/Simple-examples-cec8f09d37aa45b7bba76fd78077e2b9)
+
+[Panini+PostgreSQL](https://www.notion.so/Panini-PostgreSQL-6fc2920642774eceb97f4c7e489affe1)
+
 [1 million requests](https://www.notion.so/1-million-requests-82ca5bb0c1ac417e8cae42a76fdea201)
\ No newline at end of file
diff --git a/docs/HTTP_server.md b/docs/HTTP_server.md
index a18b8f7..657f7b1 100644
--- a/docs/HTTP_server.md
+++ b/docs/HTTP_server.md
@@ -1,6 +1,8 @@
-If a NATS network is not enough for you and you need an HTTP web server in one microservice with NATS, I have to tell you Panini can do it. For HTTP, Panini uses [aiohttp] ([https://github.com/aio-libs/aiohttp](https://github.com/aio-libs/aiohttp)) as the web server. 
+In some cases a NATS network is not enough for microservice's requirements and engineers may need an HTTP web server in one microservice with NATS. For an HTTP server, Panini uses [Aiohttp](https://github.com/aio-libs/aiohttp). It might be useful if you need to handle HTTP requests from a frontend or other microservices to create a gateway between HTTP and NATS spaces.
 
-Panini microservice with HTTP endpoint, might look like below:
+In order to run a HTTP server you have to call <span class="red">`app.setup_web_server`</span> with host, port, and other parameters that you want to pass to Aiohttp, for example, <span class="red">`ssl_context`</span> , <span class="red">`shutdown_timeout`</span> or <span class="red">`access_log`</span>.
+
+Panini microservice with HTTP endpoint might look like this:
 
 ```python
 from aiohttp import web
@@ -10,12 +12,10 @@ app = panini_app.App(
         service_name='nats-microservice-that-also-http',
         host='127.0.0.1',
         port=4222,
-				web_server=True,
-				web_host='127.0.0.1',
-        web_port=5000,
 )
+app.setup_web_server(port=5000)
 
-# app logic, NATS listeners & tasks
+# NATS listeners & tasks
 
 @app.http.get("/test")
 async def web_endpoint_listener(request):
@@ -28,7 +28,7 @@ if __name__ == "__main__":
     app.start()
 ```
 
-Use the aiohttp syntax as a decorator after @app.http, for example aiohttp code:
+As you can see it uses <span class="red">`@app.http.get`</span> for HTTP.  Panini uses Aiohttp under the hood, in other words <span class="red">`@app.http`</span> is <span class="red">`routes`</span> from Aiohttp. Let's take a look at an example with some Aiohttp code(without Panini): 
 
 ```python
 routes = web.RouteTableDef()
@@ -44,7 +44,13 @@ async def handle_post(request):
 app.router.add_routes(routes)
 ```
 
-For Panini it's equal to:
+Whats going on here:
+
+1. Getting <span class="red">`routes`</span> object in the first line of code
+2. <span class="red">`@routes.get('/get')`</span> bound to routes GET endpoint with URI <span class="red">`/get`</span>
+3. <span class="red">`@routes.post('/post')`</span> bound to routes POST endpoint with URI <span class="red">`/post`</span>
+
+For Panini, it's equal to:
 
 ```python
 @app.http.get('/get')
@@ -58,22 +64,25 @@ async def handle_post(request):
 # no need to call 'app.router.add_routes(routes)'
 ```
 
-It works in the same way for all "routes" functions. Let's say you want to build little REST API in Panini:
+1. <span class="red">`@app.http.get('/get')`</span> is the same as <span class="red">`@routes.get('/get')`</span>
+2. <span class="red">`@app.http.post('/post')`</span> is the same as <span class="red">`@routes.post('/post')`</span>
+
+You can also use the rest of the syntax of Aoihttp in Panini with <span class="red">`@app.http`</span> . In fact, Panini just creates an instance of Aiohttp inside, therefore <span class="red">`@app.http`</span>  is <span class="red">`web.RouteTableDef()`</span>  from Aiohttp. 
+
+Let's check how to build a little REST API in Panini:
 
 ```python
-.from aiohttp import web
+from aiohttp import web
 from panini import  app as panini_app
 
 app = panini_app.App(
         service_name='nats-microservice-that-also-http-rest',
         host='127.0.0.1',
         port=4222,
-				web_server=True,
-				web_host='127.0.0.1',
-        web_port=5000,
 )
+app.setup_web_server(port=5000)
 
-@app.timer_task(interval=2)
+@app.timer_task(interval=2)  
 async def publish():
     for _ in range(10):
         await app.publish(subject="some.publish.subject", message={'some':'message'})
@@ -96,4 +105,6 @@ if __name__ == "__main__":
     app.start()
 ```
 
-That's it. Next step - websocket server
\ No newline at end of file
+This example demonstrates how to build a class-based view from Aiohttp. To know more about Aiohttp, we recommend checking its documentation: [https://docs.aiohttp.org/en/stable/web_quickstart.html#resource-views](https://docs.aiohttp.org/en/stable/web_quickstart.html#resource-views) 
+
+That's it. Next step - WebSocket server.
\ No newline at end of file
diff --git a/docs/How_Panini_works.md b/docs/How_Panini_works.md
index 1c54b0b..2cfc5ad 100644
--- a/docs/How_Panini_works.md
+++ b/docs/How_Panini_works.md
@@ -1,7 +1,9 @@
-[First steps](https://www.notion.so/First-steps-0b383bd35b6c41288e1c0c67761586e3)
+If this is your first time here, we recommended installing NATS and going through the quick start before exploring these sections. The current section describes the main components, their parameters, and their attributes:
 
-[Arguments panini app takes](https://www.notion.so/Arguments-panini-app-takes-2430e8f542a34fb1a6c16aaf430195dd)
+[Panini app arguments](https://www.notion.so/Panini-app-arguments-0f470c33c602496cbfbdf9316f1a7001)
 
-[Main functions of panini app](https://www.notion.so/Main-functions-of-panini-app-7f27bef631334c048a5b47e4a03a9538)
+[Panini app functions](https://www.notion.so/Panini-app-functions-80d79387cd3043d4b5191df45ab184ab)
 
-[Incoming message structure](https://www.notion.so/Incoming-message-structure-88a753cffbec434e907ebb9479e26a28)
\ No newline at end of file
+[Incoming message structure](https://www.notion.so/Incoming-message-structure-98d7e32053b04cdca48aa00e79a95b7a)
+
+[How to validate that microservice connected to NATS](https://www.notion.so/How-to-validate-that-microservice-connected-to-NATS-a4b8db64b64e4e2d8b192dc3dad2764c)
\ No newline at end of file
diff --git a/docs/How_arguments.md b/docs/How_arguments.md
deleted file mode 100644
index f73c3e9..0000000
--- a/docs/How_arguments.md
+++ /dev/null
@@ -1,18 +0,0 @@
-- *host*: NATS hostname
-- *port*: NATS port
-- *service_name*: name of microservice, will be the part of NATS client_id if you didn't set custom client_id
-- *client_id*: custom NATS client_id
-- *tasks*: list of asyncio tasks that you want to run along with NATS client, each task equal to decorator `@app.task()`
-- *reconnect*: if lost connection to NATS broker
-- *max_reconnect_attempts*: pause between reconnection
-- *reconnecting_time_sleep*: pause between reconnection
-- *subscribe_subjects_and_callbacks*: asyncio callbacks with subjects to subscribe, each callbacks equal to decorator `@app.listen("some.subject")`
-- *allocation_queue_group*: the name of queue_group, incoming traffic allocates between allocation_queue_group's members, more detailed here
-- *listen_subject_only_if_include*: list of strings. If argument included, panini subscribes to NATS subjects only if it's matches at least one of them
-- *web_server*: boolean flag, True means to run aiohttp along with NATS client
-- *web_app*: custom aiohttp app
-- *web_host*: host of aiohttp server
-- *web_port*: port of aiohttp server
-- *logger_required*: Is logger required for the project, if not - EmptyLogger will be provided
-- *logger_files_path*: main path for logs
-- *logger_in_separate_process*: use log in the same or in different process
\ No newline at end of file
diff --git a/docs/How_incoming_msg.md b/docs/How_incoming_msg.md
deleted file mode 100644
index f2885d2..0000000
--- a/docs/How_incoming_msg.md
+++ /dev/null
@@ -1,9 +0,0 @@
-message.subject - NATS subject that message comes from, analogue to Kafka topic or HTTP url
-
-[message.data](http://message.data) - analogue of HTTP message body
-
-message.reply - one time use subject to send response
-
-message.sid - subscribe ID that message comes from
-
-message.context - any custom dict
\ No newline at end of file
diff --git a/docs/How_main_func.md b/docs/How_main_func.md
deleted file mode 100644
index 8e02c12..0000000
--- a/docs/How_main_func.md
+++ /dev/null
@@ -1,167 +0,0 @@
-### Decorators
-
-@app.task() - an asyncio task that is launched with the application, executed once
-
-usage example:
-
-```python
-@app.task()
-async def publish():
-    while True:
-        message = get_some_update()
-        await app.publish(subject='some.subject', message=message)
-```
-
-supported arguments
-
-@app.timer_task(interval=1) - asyncio task that will run at a given interval
-
-usage example:
-
-```python
-@app.timer_task(interval=2)
-async def your_periodic_task():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message={'some':'data'})
-```
-
-@app.listen("some.subject") - subscribe to the specified subject and run a callback for every incoming message
-
-usage example:
-
-```python
-@app.listen('some.subject')
-async def subject_for_requests_listener(msg):
-    subject = msg.subject
-    message = msg.data
-    # handle incoming message
-```
-
-### Functions
-
-general argument defenitions
-
-- subject: NATS subject to send or subscribe
-- message: NATS single message body to send or receive
-- reply_to: additional subject for response, relevant if you want to request from one microservice but receive by another one
-- force: this flag trigger immediate send all messages in buffer, otherwise it send periodically. Sending frequency depends on how many asyncio tasks running in loop
-- data_type: allows to chose type of message body to send or receive, supports `json.dumps`(default for sending messages), `json.loads`(default for received messages), `str`, `bytes`. More detailed here
-- timeout: only for requests, equal to http request timeout
-- ssid: subscription ID
-
-app.publish
-
-usage example
-
-```python
-await app.publish(subject='some.subject', message={'some':'message'})
-```
-
-supported arguments:
-
-- subject
-- message
-- reply_to
-- force
-- data_type
-
-response - None
-
-app.request
-
-```python
-response = await app.reqeust(subject='some.subject', message={'some':'message'})
-```
-
-supported arguments:
-
-- subject
-- message
-- timeout
-- data_type
-
-response - message body, type depends on given data_type
-
-app.publish_from_another_thread
-
-```python
-app.publish_from_another_thread(subject='some.subject', message={'some':'message'})
-```
-
-supported arguments:
-
-- subject
-- message
-- reply_to
-- force
-- data_type
-
-response - None
-
-app.request_from_another_thread
-
-```python
-response = await app.request_from_another_thread(subject='some.subject', message={'some':'message'})
-```
-
-supported arguments:
-
-- subject
-- message
-- timeout
-- data_type
-
-response - message body, type depends on given data_type
-
-app.subscribe_subject
-
-```python
-await app.subscribe_subject(subject='some.subject', callback=some_funcion)
-```
-
-supported arguments:
-
-- subject
-- callback
-
-response - subscription ID
-
-app.unsubscribe_subject
-
-```python
-await app.unsubscribe_subject(subject='some.subject')
-```
-
-supported arguments:
-
-- subject
-
-response - None
-
-app.unsubscribe_ssid
-
-```python
-await app.unsubscribe_ssid(ssid='some.subject')
-```
-
-supported arguments:
-
-- ssid
-
-response - None
-
-#TODO: app.publish_sync
-
-#TODO: app.request_sync
-
-#TODO: app.request_from_another_thread_sync
-
-#TODO: app.publish_from_another_thread_sync
-
-#TODO: app.subscribe_subject_sync
-
-#TODO: app.unsubscribe_subject_sync
-
-#TODO: app.disconnect
-
-#TODO: middlewares
\ No newline at end of file
diff --git a/docs/How_to_contribute.md b/docs/How_to_contribute.md
index d01a97d..f9f7c0c 100644
--- a/docs/How_to_contribute.md
+++ b/docs/How_to_contribute.md
@@ -1,21 +1,21 @@
-### **Welcome contributor!**
-
-First off, thank you for considering contributing to Panini. We are looking for developers to make Panini a great project.
-
-### **Why you need to read your guidelines.**
-
-Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.
-
-# **Your First Contribution**
-
-Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
-
-Well, how can you help:
-
-- suggest new updates [here](https://github.com/lwinterface/panini/issues)
-- review a [pull request](https://github.com/lwinterface/panini/pulls)
-- fix an [issue](https://github.com/lwinterface/panini/issues)
-- write a tutorial
-- always follow by [this](https://github.com/firstcontributions/first-contributions) guide for your contributions
-
+## **Welcome contributor!**
+
+First off, thank you for considering contributing to Panini. We are looking for developers to make Panini a great project.
+
+## **Why you need to read our guidelines.**
+
+Following these guidelines ensures that you respect the time of the developers managing and developing this open-source project. In return, they reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests.
+
+## **Your First Contribution**
+
+Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
+
+Well, how can you help:
+
+- suggest new updates [here](https://github.com/lwinterface/panini/issues)
+- review a [pull request](https://github.com/lwinterface/panini/pulls)
+- fix an [issue](https://github.com/lwinterface/panini/issues)
+- write a tutorial
+- always follow [this](https://github.com/firstcontributions/first-contributions) guide for your contributions
+
 At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first 😸
\ No newline at end of file
diff --git a/docs/Incoming_Message_Structure.md b/docs/Incoming_Message_Structure.md
new file mode 100644
index 0000000..e108a1e
--- /dev/null
+++ b/docs/Incoming_Message_Structure.md
@@ -0,0 +1,19 @@
+Usage example:
+
+```python
+@app.listen(subject='some.subject')
+async def subject_for_requests_listener(msg):
+    subject = msg.subject
+    message = msg.data
+    # handle incoming message
+```
+
+**msg.subject**(*str*) - NATS subject that message comes from
+
+**msg.data**(*dict or str or bytes*) - analogue of HTTP message's body
+
+**msg.reply**(*str*) - one-time use subject to send a response
+
+**msg.sid**(*str*) - subscribe ID that message comes from
+
+**msg.context**(*dict*) - any custom dict for internal use
\ No newline at end of file
diff --git a/docs/Index.md b/docs/Index.md
deleted file mode 100644
index 3f72880..0000000
--- a/docs/Index.md
+++ /dev/null
@@ -1,4 +0,0 @@
-# Panini Docks
-## User's guide
-
-some introduction text
\ No newline at end of file
diff --git a/docs/Install.md b/docs/Install.md
index 8d684b6..5bceb1a 100644
--- a/docs/Install.md
+++ b/docs/Install.md
@@ -1,56 +1,80 @@
-# Install
-### Install Panini
-
-Supported Python≥3.8
-
-Install via pip:
-```bash
-> pip install panini
-```
-Install via GitHub:
-
-```bash
-> git clone https://github.com/lwinterface/panini.git
-```
-
-### Install NATS
-
-Install via Docker:
-
-```python
-> docker pull nats:latest
-```
-
-On Windows:
-
-```python
-> choco install nats-server
-```
-
-On Mac OS:
-
-```python
-> brew install nats-server
-```
-
-### Run NATS
-
-Just run command:
-
-```python
-> nats-server
-```
-
-Or run broker via docker-compose. From project directory that includes file [docker-compose.yml](https://github.com/lwinterface/panini/blob/master/docker-compose.yml). Command below:
-
-```python
-> docker-compose up
-```
-
-Stop broker when you finish work with it:
-
-```python
-> docker-compose down
-```
-
-Note, for production we recommend running the broker with dockerized microservices. Example of dockerized panini project here
\ No newline at end of file
+
+### Prerequisites:
+
+Before getting started make sure you have all the prerequisites installed:
+
+- Python
+- pip
+- NATS
+- Docker (only if you want to run NATS via Docker)
+
+Panini supports Python≥3.8, we recommend using the latest version
+
+NATS should be installed as the [documentation suggests](https://docs.nats.io/nats-server/installation), one of the ways is described below. 
+
+After installation, you need to run NATS.
+
+  
+
+### Installing NATS
+
+Install via Docker:
+
+```python
+> docker pull nats:latest
+```
+
+On Windows:
+
+```python
+> choco install nats-server
+```
+
+On Mac OS:
+
+```python
+> brew install nats-server
+```
+
+### Running NATS
+
+Simply run the following command:
+
+```python
+> nats-server
+[1281] 2021/11/01 07:19:20.762736 [INF] Starting nats-server version 2.1.6
+[1281] 2021/11/01 07:19:20.762853 [INF] Git commit [not set]
+[1281] 2021/11/01 07:19:20.763182 [INF] Listening for client connections on 0.0.0.0:4222
+[1281] 2021/11/01 07:19:20.763190 [INF] Server id is NBIDLI72N7ONJZSSFKLL774A7RWCKVHU26X2QI7RFOETJFURXA6CETRB
+[1281] 2021/11/01 07:19:20.763192 [INF] Server is ready
+```
+
+### Installing Panini
+
+Install Panini via pip:
+
+```bash
+> pip install panini
+```
+
+Check that Panini has been installed
+
+```bash
+> pip show panini
+Name: panini
+Version: 0.6.0
+Summary: A python messaging framework for microservices based on NATS
+Home-page: https://github.com/lwinterface/panini
+Author: Op Return SA, developers: Andrii Volotskov, Danylo Tiutiushkin, Oleksii Volotskov
+Author-email: example@example.com
+License: MIT
+Location: /Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages
+Requires: six, requests, python-json-logger, async-timeout, asyncio-nats-client, prometheus-client, websocket-client, nest-asyncio, nats-python, ujson, yarl, aiohttp, aiohttp-cors
+Required-by:
+```
+
+Install Panini via GitHub:
+
+```bash
+> git clone https://github.com/lwinterface/panini.git
+```
\ No newline at end of file
diff --git a/docs/License.md b/docs/License.md
index 8f82e0c..c8cc227 100644
--- a/docs/License.md
+++ b/docs/License.md
@@ -1,9 +1,21 @@
-# **The MIT License**
-
-Copyright (c) 2018 Chris Ostrouchov
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
\ No newline at end of file
+## MIT License
+
+Copyright (c) 2021 lwinterface
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/docs/Logger.md b/docs/Logger.md
index 40b2769..3cab36a 100644
--- a/docs/Logger.md
+++ b/docs/Logger.md
@@ -1,7 +1,10 @@
-# Logger
+What is the Panini internal logger? 
+
+It is a default python logger with some settings that the Panini team found convenient. If you are using your own logging style, just skip this section.
+
 Logger has 2 important parameters:
 
-- logger_files_path - the path to the log folder - can be absolute or relative (will relate to the app root path):
+- <span class="red">logger_files_path</span> - the path to the log folder - it can be absolute or relative (will relate to the app root path):
 
 ```python
 from panini import app as panini_app
@@ -18,7 +21,7 @@ log = app.logger
 log.info("some log")  # write log
 ```
 
-- in_separate_process - specify, if you want logger to be as separate process or to log in main process:
+- <span class="red">in_separate_process</span> - specify, if you want logger in a separate process or in the main process:
 
 ```python
 from panini import app as panini_app
@@ -38,7 +41,7 @@ log.info("some log")  # write log
 
 # Log Config:
 
-In the logger we use simple default config, that fit majority of logger needs:
+In the logger we use a simple default config that fits most logger needs:
 
 ```json
 `{
@@ -140,14 +143,13 @@ In the logger we use simple default config, that fit majority of logger needs:
 ```
 
 Also, you can provide a custom log configuration for advanced logging.
-To do that - just create `config/log_config.json` file inside your app root path.
+To do that - just create <span class="red">`config/log_config.json`</span> file inside your app root path.
 (see [log_config.json.sample](https://github.com/lwinterface/panini/blob/master/examples/simple_examples/config/log_config.json.sample))
 
 *Please notice, that some formatters and loggers will be added to your custom config,
-but not overwritten if they exist (such as panini and inter_services_request loggers
-and detailed formatter)*
+but not overwritten if they exist.*
 
-You can also provide some keywords to custom log config file, that will be replaced to
+You can also provide some keywords to the custom log config file that will be replaced by
 some meaningful data, such as:
 
 ```
@@ -157,11 +159,11 @@ some meaningful data, such as:
 
 ```
 
-*Notice, that this keywords works only inside "filename" log configuration*
+*Notice, that these keywords work only inside the "filename" log configuration.*
 
-## Additional logger information:
+## Additional logger settings:
 
-If you want to be able to log some *extra* parameters, the things should be done as next:
+If you want to be able to log some *extra* parameters, these things should be done next:
 
 ```python
 from panini.utils.logger import get_logger
@@ -171,7 +173,7 @@ log = get_logger('app')
 log.warning('some log', extra_parameter='some extra parameter')
 ```
 
-That will be logged due to your config (or only in the file, as it is done by default) -
+These extra parameters will be logged with each log -
 see %(extra)s argument in the default configuration format.
 
-*Notice, that extra parameters will be added to your logs only if you add "%(extra)s" to your log config formatter*
\ No newline at end of file
+*Notice that extra parameters will be added to your logs only if you add "%(extra)s" to your log config formatter.*
\ No newline at end of file
diff --git a/docs/Middlewares.md b/docs/Middlewares.md
index 215dbe8..c773188 100644
--- a/docs/Middlewares.md
+++ b/docs/Middlewares.md
@@ -1,203 +1,235 @@
-You can add middleware to **Panini** applications.
-
-A *"middleware"* (in common meaning) is a function that works with every **request** before it is processed by any specific path operation. And also with every **response** before returning it.
-
-## Quck overview:
-
-```python
-import time
-
-from panini.middleware import Middleware
-
-class ProcessTimeMiddleware(Middleware):
-    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
-        start_time = time.time()
-        response = await request_func(subject, message, *args, **kwargs)
-        process_time = time.time() - start_time
-        response["process-time"] = str(process_time)
-        return response
-
-# app.add_middleware(ProcessTimeMiddleware)
-# response = await app.request("subject", message)
-# response: {"some_data": 12345, "process-time": '0.0051320'}
-```
-
-## Full overview
-
-In Panini we have 2 core **operations** to communicate between microservices:
-
-- send_operation (publish or request)
-- listen_operation (listen any of send_operations)
-
-Using Panini Middleware we can apply modifications to both of these operations "**before**" and "**after**" they are called.
-
-**Main idea:**
-
-- It takes each **operation** in your application.
-- It can then do something to that **operation** or run any needed code (**before** the operation is called).
-- Then it passes the **operation** to be processed by the rest of the application.
-- It then takes the **response** generated by the application.
-- It can do something to that **response** or run any needed code
-(**after** operation is called**)**.
-- Then it returns the **response.**
-
-## Middleware Interface:
-
-The basic interface of Panini Middleware looks like this:
-
-```python
-class Middleware:
-		def __init__(self, *args, **kwargs):
-        pass
-
-    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
-        """
-        :param subject: str
-        :param message: any of supported types
-        :param publish_func: Callable for publish
-        :return: None
-        """
-
-    async def listen_publish(self, msg, callback):
-        """
-        :param msg: Msg
-        :param callback: Callable, that will be called on receive message
-        :return: None
-        """
-
-    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
-        """
-        :param subject: str
-        :param message: any of supported types
-        :param request_func: Callable for request
-        :return: any of supported types
-        """
-
-    async def listen_request(self, msg, callback):
-        """
-        :param msg: Msg
-        :param callback: Callable, that will be called on receive message
-        :return: any of supported types
-        """
-
-		# and composed functions for better user experience:
-		async def send_any(self, subject: str, message, send_func, *args, **kwargs):
-        """
-        :param subject: str
-        :param message: any of supported types
-        :param send_func: Callable for send
-        :return: None or any of supported types
-        """
-
-    async def listen_any(self, msg, callback):
-        """
-        :param msg: Msg
-        :param callback: Callable, that will be called on receive message
-        :return: None or any of supported types
-        """
-```
-
-*Note here, that `send_any` will do the job for `send_request` and `send_publish`, if they are not implemented, but won't override them, if they exist. And the same stuff with `listen_any`.
-
-## Examples
-
-### ProcessTimeMiddleware
-
-To create a middleware to add `process-time` parameter to response, that contains the time in seconds that it took to send & process the request and generate a response
-
-The `send_request` function receives:
-
-- The `subject` & `message`: such as common request function.
-- A function `request_func` , that recieves subject, message & args, kwargs as parameters.
-    - This function will send `request` to the corresponding subject with message provided.
-    - This function will also call all other `middlewares` (if they exist) inside the app.
-    - Then it returns the `response` before returning it.
-- You can then modify further the `response` before returning it.
-
-```python
-import time
-
-from panini.middleware import Middleware
-
-class ProcessTimeMiddleware(Middleware):
-    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
-        start_time = time.time()
-        response = await request_func(subject, message, *args, **kwargs)
-        process_time = time.time() - start_time
-        response["process-time"] = str(process_time)
-        return response
-```
-
-We will override only `send_request` function, so each time `app.request()` is called in our application - the processing time is calculated.
-
-You just need to add ProcessTimeMiddleware before app.start()
-
-```python
-app.add_middleware(ProcessTimeMiddleware)
-```
-
-### TestingMiddleware
-
-Imagine, that you want to easily switch to *test mode* in your application, which means:
-
-- You want to always send to different subjects (the same subject, but with `test` prefix)
-- You want to remove some `meaningful data` from requests & responses
-
-```python
-from panini.middleware import Middleware
-
-class TestingMiddleware(Middleware):
-    def __init__(self, meaningful_key):
-        self.meaningful_key = meaningful_key
-
-    async def send_any(self, subject: str, message, send_func, *args, **kwargs):
-				subject = "test." + subject
-        if self.meaningful_key in message:
-            del message[self.meaningful_key]
-
-        response = await send_func(subject, message, *args, **kwargs)
-
-        if self.meaningful_key in response:
-            del response[self.meaningful_key]
-        return response
-
-    async def listen_any(self, msg, callback):
-        if self.meaningful_key in msg.data:
-            del msg.data[self.meaningful_key]
-
-        response = await callback(msg)
-
-        if self.meaningful_key in response:
-            del response[self.meaningful_key]
-        return response
-```
-
-Then, you need to add TestingMiddleware and specify the meaningful_key parameter like this:
-
-```python
-app.add_middleware(TestingMiddleware, "meaningful_key")
-```
-
-**Please, notice, that you should use `async` interface for creating Middlewares 
-(as written in examples)**
-
-## Advanced Middlewares
-
-- You can use some [built-in middlewares](https://github.com/lwinterface/panini/tree/master/panini/middleware) for common cases, already implemented in panini
-- If you want to make hierarchical middleware, with more than 1 inheritance - 
-please, re-call methods, from your base middleware:
-
-```python
-class FooMiddleware(Middleware):
-    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
-        print("In Foo Middleware: publish")
-        await publish_func(subject, message, *args, **kwargs)
-
-class BarMiddleware(FooMiddleware):
-    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
-        print("In Bar Middleware: request")
-        return await request_func(subject, message, *args, **kwargs)
-
-    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
-        return await super(BarMiddleware, self).send_publish(subject, message, *args, **kwargs)
+A *"*middleware*"* (in common meaning) is a function that works with every **request** before it is processed by a message handler. And also with every **response** before returning it. 
+
+Panini middleware works similar to Django or Flask-like frameworks, and also offers to use middleware for sending messages. 
+
+Example of an app with such middleware:
+
+```python
+import time
+from panini import app as panini_app
+from panini.middleware import Middleware
+
+app = panini_app.App(
+    service_name="another_panini_app",
+    host="127.0.0.1",
+    port=4222,
+)
+
+class ProcessTimeMiddleware(Middleware):
+    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
+        """
+        measure request processing time and add to a response
+        """
+        start_time = time.time()
+        response = await request_func(subject, message, *args, **kwargs)
+        process_time = time.time() - start_time
+        response["process-time"] = str(process_time)
+        return response
+
+@app.task(interval=1)
+async def publish_periodically():
+    message = {"request_type":"Bitcoin price"}
+    result = await app.request(subject="some.request.subject", message=message)
+    # received response: {"bitcoin-price": 100000, "process-time": '0.0051320'}
+    print(f"requested Bitcoin price, result: {result}")
+
+@app.listen("some.publish.subject")
+async def receive_messages(msg):
+    print("received request for Bitcoin price")
+    bitcoin_price = 100000
+    # response to send: {"bitcoin-price": 100000}
+    return {"bitcoin-price": bitcoin_price}
+
+if __name__ == "__main__":
+    app.add_middleware(ProcessTimeMiddleware)
+    app.start()
+
+```
+
+As you can see <span class="red">`ProcessTimeMiddleware`</span> measures the request time and adds it to the response in the <span class="red">`process-time`</span> field.
+
+Of course, in real tasks, the listener will be in another microservice. We put the task and the listener in one microservice to simplify this example.
+
+## Middleware detailed
+
+In Panini we have 2 core **operations** to communicate between microservices:
+
+- send operation (publish or request)
+- listen operation (listen to any of send operations)
+
+Using Panini Middleware we can apply modifications to both of these operations "**before**" and "**after**" they are called.
+
+**How it works:**
+
+- It takes each **operation** in your application.
+- It can then do something to that **operation** or run any needed code (**before** the operation is called).
+- Then it passes the **operation** to be processed by the rest of the application.
+- It then takes the **response** generated by the application.
+- It can do something to that **response** or run any needed code
+(**after** an operation is called**)**.
+- Then it returns the **response.**
+
+## Middleware Interface:
+
+The basic interface of Panini Middleware looks like this:
+
+```python
+class Middleware:
+		def __init__(self, *args, **kwargs):
+        pass
+
+    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
+        """
+        :param subject: str
+        :param message: any of supported types
+        :param publish_func: Callable for publish
+        :return: None
+        """
+
+    async def listen_publish(self, msg, callback):
+        """
+        :param msg: Msg
+        :param callback: Callable, that will be called on receive message
+        :return: None
+        """
+
+    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
+        """
+        :param subject: str
+        :param message: any of supported types
+        :param request_func: Callable for request
+        :return: any of supported types
+        """
+
+    async def listen_request(self, msg, callback):
+        """
+        :param msg: Msg
+        :param callback: Callable, that will be called on receive message
+        :return: any of supported types
+        """
+
+		# and composed functions for better user experience:
+		async def send_any(self, subject: str, message, send_func, *args, **kwargs):
+        """
+        :param subject: str
+        :param message: any of supported types
+        :param send_func: Callable for send
+        :return: None or any of supported types
+        """
+
+    async def listen_any(self, msg, callback):
+        """
+        :param msg: Msg
+        :param callback: Callable, that will be called on receive message
+        :return: None or any of supported types
+        """
+```
+
+*Note here, that <span class="red">`send_any`</span> will do the job for <span class="red">`send_request`</span> and <span class="red">`send_publish`</span>, if they are not implemented, but won't override them, if they exist. And the same thing with <span class="red">`listen_any`</span>.
+
+## Examples
+
+### ProcessTimeMiddleware
+
+Let's check closer the very first example in this section. That middleware adds a <span class="red">`process-time`</span> parameter to a response. The <span class="red">`process-time`</span> parameter contains the time in seconds it takes to send & process the request. The <span class="red">`send_request`</span> function receives:
+
+- The <span class="red">`subject`</span> & <span class="red">`message`</span>: such as common request function.
+- A function <span class="red">`request_func`</span>, that receives subject, message & args/kwargs.
+    - This function will send a **request** to the corresponding subject with a message provided.
+    - This function will also call all other middlewares (if they exist) inside the app.
+    - Then it returns the **response** before returning it.
+- You can further modify the **response** before returning it.
+
+```python
+import time
+
+from panini.middleware import Middleware
+
+class ProcessTimeMiddleware(Middleware):
+    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
+        start_time = time.time()
+        response = await request_func(subject, message, *args, **kwargs)
+        process_time = time.time() - start_time
+        response["process-time"] = str(process_time)
+        return response
+```
+
+To register middleware it needs to add ProcessTimeMiddleware before <span class="red">`app.start()`</span>
+
+```python
+app.add_middleware(ProcessTimeMiddleware)
+
+```
+
+### TestingMiddleware
+
+Imagine, that you want to easily switch to *test mode* in your application, which means:
+
+- You want to always send it to different subjects (the same subject, but with <span class="red">`test`</span> prefix)
+- You want to remove some meaningful data from requests & responses
+
+```python
+from panini.middleware import Middleware
+
+class TestingMiddleware(Middleware):
+    def __init__(self, meaningful_key):
+        self.meaningful_key = meaningful_key
+
+    async def send_any(self, subject: str, message, send_func, *args, **kwargs):
+				subject = "test." + subject
+        if self.meaningful_key in message:
+            del message[self.meaningful_key]
+
+        response = await send_func(subject, message, *args, **kwargs)
+
+        if self.meaningful_key in response:
+            del response[self.meaningful_key]
+        return response
+
+    async def listen_any(self, msg, callback):
+        if self.meaningful_key in msg.data:
+            del msg.data[self.meaningful_key]
+
+        response = await callback(msg)
+
+        if self.meaningful_key in response:
+            del response[self.meaningful_key]
+        return response
+```
+
+Then, you need to add TestingMiddleware and specify the <span class="red">`meaningful_key`</span> parameter like this:
+
+```python
+app.add_middleware(TestingMiddleware, "meaningful_key")
+```
+<div class="attention">
+<p class="attention__emoji-icon">💡</p><p>Please, notice, that you should only use <span class="red">async</span> interface for creating Middlewares as written in examples. The synchronous interface doesn't support middleware. But <span class="red">add_middleware</span> is called synchronously as in the example above</p>
+</div>
+
+## Built-in-middlewares
+
+You can use some [built-in middlewares](https://github.com/lwinterface/panini/tree/master/panini/middleware) for common cases, already implemented in Panini:
+
+- <span class="red">`ErrorMiddleware`</span> - call callback when an error rises
+- <span class="red">`NATSTimeoutMiddleware`</span> - logs NATS timeout
+- <span class="red">`PrometheusMonitoringMiddleware`</span> - measure performance of app, more detailed <here>
+
+## Hierarchical Middlewares
+
+If you want to make hierarchical middleware, with more than 1 inheritance - 
+please, recall methods, from your base middleware:
+
+```python
+class FooMiddleware(Middleware):
+    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
+        print("In Foo Middleware: publish")
+        await publish_func(subject, message, *args, **kwargs)
+
+class BarMiddleware(FooMiddleware):
+    async def send_request(self, subject: str, message, request_func, *args, **kwargs):
+        print("In Bar Middleware: request")
+				return await request_func(subject, message, *args, **kwargs)
+
+    async def send_publish(self, subject: str, message, publish_func, *args, **kwargs):
+        return await super(BarMiddleware, self).send_publish(subject, message, *args, **kwargs)
 ```
\ No newline at end of file
diff --git a/docs/Monitoring_application.md b/docs/Monitoring_application.md
deleted file mode 100644
index 438caea..0000000
--- a/docs/Monitoring_application.md
+++ /dev/null
@@ -1,46 +0,0 @@
-### Overview
-
-In terms of microservices, when it is a bit harder to get the full image of what is going on, we decided to implement the built-in possibility to watch the performance and find out problems or bugs in the system easier and faster.
-
-For this stuff we are using:
-
-- [**Prometheus** ](https://prometheus.io/)time-series database & monitoring solution.
-- [**Grafana** ](https://grafana.com/) for user-friendly dashboards.
-- Panini built-in [**middleware** ](https://www.notion.so/Middlewares-a9dcd04247514c9cb05b9823f68b223b) [PrometheusMonitoringMiddleware](https://github.com/lwinterface/panini/blob/master/panini/middleware/prometheus_monitoring.py)
-
-The main idea of **PrometheusMonitoringMiddleware** is to collect important metrix, such as:
-
-- Message **rate** by subject
-- Request **status** (success or failure)
-- Request **latency**
-
-After collecting, it will pereodically send these metrixes to [Prometheus Gateway](https://prometheus.io/docs/instrumenting/pushing/), so you will see the changes in your Grafana dashboard *live.*
-
-### Initialize monitoring in Panini application
-
-Suppose, that we already have **Prometheus** & **Grafana** up and running.
-
-After that, we need to add **PrometheusMonitoringMiddleware** to the app:
-
-```python
-from panini.middleware.prometheus_middleware import PrometheusMonitoringMiddleware
-
-app.add_middleware(PrometheusMonitoringMiddleware)
-# you could also specify some custom parameters (see details in middleware constructor):
-# app.add_middleware(PrometheusMonitoringMiddleware, PROMETHEUS_GATEWAY, frequency=30)
-```
-
-**PrometheusMonitoringMiddleware** parameters:
-
-- pushgateway_url: str = "[localhost:9091](http://localhost:9091)" - url to Prometheus PushGateway
-- app = None - panini App, from which the
-
-After this, all important activity of your application will be stored in Prometheus and user-friendly demonstrated in Grafana dashboard.
-
-### How to use Grafana to watch it
-
-Below our [dashboard](https://github.com/lwinterface/panini/tree/master/grafana_dashboard) that you can get here:
-
-![https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fb1765716-a820-4878-8112-fa05fb301aa2%2FScreenshot_2021-04-03_at_18.50.32.png?table=block&id=da0151aa-b974-4ca2-882c-a239923fe5ce&width=3070&userId=&cache=v2](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fb1765716-a820-4878-8112-fa05fb301aa2%2FScreenshot_2021-04-03_at_18.50.32.png?table=block&id=da0151aa-b974-4ca2-882c-a239923fe5ce&width=3070&userId=&cache=v2)
-
-You can just use our template, or dig into Grafana for more custom approach.
\ No newline at end of file
diff --git a/docs/Panini_App_Arguments.md b/docs/Panini_App_Arguments.md
new file mode 100644
index 0000000..42df148
--- /dev/null
+++ b/docs/Panini_App_Arguments.md
@@ -0,0 +1,30 @@
+Panini app available on <span class="red">`panini.app.App`</span>
+
+Usage example:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+        service_name='listener_app',
+        host='127.0.0.1',
+        port=4222,
+)
+```
+
+Parameters:
+
+- **host**(*str*): NATS broker host
+- **port**(*int*): NATS broker port
+- **service_name**(*str*): Name of microservice, will be the part of NATS client_id if you didn't set custom client_id
+- **servers**(*list*): Alternative to NATS broker host+NATS broker port. Allows microservice to establish a connection to multiple NATS brokers
+- **client_id**(*str*): Custom NATS client_id
+- **reconnect**(*bool*): Connects again if a lost connection to NATS broker
+- **max_reconnect_attempts**(*int*): Number of attempts to reconnect
+- **reconnecting_time_sleep**(*int*): Pause between reconnections
+- **allocation_queue_group**(*str*): Name of the queue group. Incoming traffic allocates between group members.
+- **logger_required**(*bool*): Logger required for the project, if not - default logger will be provided
+- **logger_files_path**(*str*): Main path for logs
+- **logger_in_separate_process**(*bool*): Use log in the same or in different process
+- **pending_bytes_limit**(*int*): Limit of bytes for a single incoming message
+- **auth**(*dict*): Dict with arguments for authentication
\ No newline at end of file
diff --git a/docs/Panini_App_Functions.md b/docs/Panini_App_Functions.md
new file mode 100644
index 0000000..9b757aa
--- /dev/null
+++ b/docs/Panini_App_Functions.md
@@ -0,0 +1,262 @@
+### Decorators
+
+<span class="red">`@app.task()`</span> - an asyncio task that is launched with the application, executed once
+
+Usage example:
+
+```python
+@app.task()
+async def publish():
+    while True:
+        message = get_some_update()
+        await app.publish(subject='some.subject', message=message)
+```
+
+<span class="red">`@app.task(interval=1)`</span> - same asyncio task but runs at a given interval
+
+Usage example:
+
+```python
+@app.task(interval=2)
+async def your_periodic_task():
+    for _ in range(10):
+        await app.publish(subject='some.publish.subject', message={'some':'data'})
+```
+
+Supported arguments:
+
+- **interval**(*int or float*): waiting time between periodic task executions
+
+<span class="red">`@app.listen(subject="some.subject")`</span> - subscribe to the specified subject and run a callback for every incoming message
+
+Usage example:
+
+```python
+@app.listen(subject='some.subject')
+async def subject_for_requests_listener(msg):
+    subject = msg.subject
+    message = msg.data
+    # handle incoming message
+```
+
+Supported arguments:
+
+- **subject**(*str*): NATS subject to subscribe
+
+### Functions
+
+Parameters from all functions:
+
+- **subject**(*str*): NATS subject to send or subscribe
+- **msg**(*<class 'Msg'>*): NATS single message object to receive
+- **reply_to**(*str*): An additional subject for a response, relevant if you want to request from one microservice but receive by another one
+- **force**(*bool*): This flag trigger immediately sends all messages in a buffer. Otherwise, the messages are send periodically. Sending frequency depends on how many asyncio tasks are running in the loop
+- **data_type**(*<class 'type'> or str*): allows choosing a type of message body to send or receive. More details <here>
+- **timeout**(*int or float*): only for requests, like HTTP request timeout but for NATS requests
+- **ssid**(*str*): subscription ID
+- **callback**(*CoroutineType*): function to call when received a new message
+
+<span class="dkGreen">app.publish</span>
+
+Usage example:
+
+```python
+await app.publish(subject='some.subject', message={'some':'message'})
+```
+
+Supported parameters:
+
+- **subject**
+- **message**
+- **reply_to**
+- **force**
+- **data_type**
+
+response - None
+
+<span class="dkGreen">app.request</span>
+
+Usage example:
+
+```python
+response = await app.request(subject='some.subject', message={'some':'message'})
+```
+
+Supported parameters:
+
+- **subject**
+- **message**
+- **timeout**
+- **data_type**
+
+response - message body, type depends on given data_type
+
+<span class="dkGreen">app.nats.publish_from_another_thread</span>
+
+Usage example:
+
+```python
+app.nats.publish_from_another_thread(subject='some.subject', message={'some':'message'})
+```
+
+Supported arguments:
+
+- **subject**
+- **message**
+- **reply_to**
+- **force**
+- **data_type**
+
+response - None
+
+<span class="dkGreen">app.request_from_another_thread</span>
+
+Usage example:
+
+```python
+response = await app.request_from_another_thread(subject='some.subject', message={'some':'message'})
+```
+
+Supported parameters:
+
+- **subject**
+- **message**
+- **timeout**
+- **data_type**
+
+response - message body, type depends on given data_type
+
+<span class="dkGreen">app.subscribe_new_subject</span>
+
+Usage example:
+
+```python
+await app.subscribe_new_subject(subject='some.subject', callback=some_funcion)
+```
+
+Supported arguments:
+
+- **subject**
+- **callback**
+
+response - subscription ID
+
+<span class="dkGreen">app.unsubscribe_subject</span>
+
+Usage example:
+
+```python
+await app.unsubscribe_subject(subject='some.subject')
+```
+
+Supported arguments:
+
+- **subject**
+
+response - None
+
+<span class="dkGreen">app.unsubscribe_ssid</span>
+
+Usage example:
+
+```python
+await app.unsubscribe_ssid(ssid='some.subject')
+```
+
+Supported arguments:
+
+- **ssid**
+
+response - None
+
+<span class="dkGreen">app.disconnect</span>
+
+Usage example:
+
+```python
+await app.disconnect()
+```
+
+<span class="dkGreen">app.publish_sync</span>
+
+Usage example:
+
+```python
+app.publish_syn(subject='some.subject', message={'some':'message'})
+```
+
+Supported parameters:
+
+- **subject**
+- **message**
+- **reply_to**
+- **force**
+- **data_type**
+
+response - None
+
+<span class="dkGreen">app.request_sync</span>
+
+Usage example:
+
+```python
+await app.request_sync(subject='some.subject', message={'some':'message'})
+```
+
+Supported parameters:
+
+- **subject**
+- **message**
+- **timeout**
+- **data_type**
+
+response - message body, type depends on given data_type
+
+<span class="dkGreen">app.subscribe_new_subject_sync</span>
+
+Usage example:
+
+```python
+await app.subscribe_new_subject(subject='some.subject', callback=some_funcion)
+```
+
+Supported arguments:
+
+- **subject**
+- **callback**
+
+response - subscription ID
+
+<span class="dkGreen">app.unsubscribe_subject_sync</span>
+
+Usage example:
+
+```python
+await app.unsubscribe_subject(subject='some.subject')
+```
+
+Supported arguments:
+
+- **subject**
+
+response - None
+
+<span class="dkGreen">app.disconnect_sync</span>
+
+Usage example:
+
+```python
+app.disconnect_sync()
+```
+
+app.middlewares
+
+Usage example:
+
+```python
+app.add_middleware(SomeMiddleware)
+```
+
+Supported arguments:
+
+- cls(*class 'Middleware'*)
\ No newline at end of file
diff --git a/docs/How_first_step.md b/docs/Panini_Validation.md
similarity index 54%
rename from docs/How_first_step.md
rename to docs/Panini_Validation.md
index fe19aa5..cca5f89 100644
--- a/docs/How_first_step.md
+++ b/docs/Panini_Validation.md
@@ -1,78 +1,84 @@
-1. Make sure that NATS server is running, how to run it here
-2. Create simple app might looks like module below
-
-module `app.py`:
-
-```python
-from panini import  app as panini_app
-
-app = panini_app.App(
-        service_name='quickstart-app',
-        host='127.0.0.1',
-        port=4222,
-)
-
-@app.timer_task(interval=1)
-async def publish_periodically():
-        await app.publish(subject="some.publish.subject", message={'some_key':'Hello stream world'})
-
-@app.listen("some.request.subject")
-async def receive_messages(msg):
-    subject = msg.subject
-    message = msg.data
-		print('Yai! I got new message from stream: {subject}:{message}')
-
-if __name__ == "__main__":
-    app.start()
-```
-
-It's includes: 
-
-- one timer_task that each second sends message {'some_key':'Hello stream world'}
-- one request endpoint - like HTTP endpoints but for
-
-3. Run Panini. Let's 
-
-```python
-python3 app.py
-```
-
-Check panini app as client of NATS broker:
-
-```python
-curl http://127.0.0.1:8222/connz
-```
-
-you should get something like:
-
-```python
-{
-  "server_id": "NAKBPTP3AFN3XG4CJBMARGTJPWQS6M6DH2OZ4S5F6MAV4DXEJKXNIVC6",
-  "now": "2021-05-02T22:36:11.940857451Z",
-  "num_connections": 1,
-  "total": 1,
-  "offset": 0,
-  "limit": 1024,
-  "connections": [
-    {
-      "cid": 1,
-      "ip": "192.168.240.21",
-      "port": 35454,
-      "start": "2021-04-27T18:05:31.848989012Z",
-      "last_activity": "2021-05-02T22:36:11.882566724Z",
-      "rtt": "214µs",
-      "uptime": "5d4h30m40s",
-      "idle": "0s",
-      "pending_bytes": 0,
-      "in_msgs": 4,
-      "out_msgs": 4,
-      "in_bytes": 345,
-      "out_bytes": 345,
-      "subscriptions": 1,
-      "name": "quickstart-app__c37ddb8038af__556401",
-      "lang": "python3",
-      "version": "0.9.2"
-    }
-```
-
-All your microservices under flag `connections`
\ No newline at end of file
+1. Make sure that NATS server is running, how to run it <here>
+2. Create a simple app that periodically sends a message by one asyncio task and receives the message by another task. The whole app is a single module  <span class="red">`app.py`</span>:
+
+```python
+from panini import  app as panini_app
+
+app = panini_app.App(
+        service_name='quickstart-app',
+        host='127.0.0.1',
+        port=4222,
+)
+
+@app.task(interval=1)
+async def publish_periodically():
+        await app.publish(subject="some.publish.subject", message={'some_key':'Hello stream world'})
+
+@app.listen("some.request.subject")
+async def receive_messages(msg):
+    subject = msg.subject
+    message = msg.data
+		print('Yai! I got new message from stream: {subject}:{message}')
+
+if __name__ == "__main__":
+    app.start()
+```
+
+3. Run *app.py*:
+
+```python
+> python3 app.py
+======================================================================================
+Panini service connected to NATS..
+id: 2
+name: quickstart-app__non_docker_env_98846__896705
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+
+Yai! I got new message from stream: {subject}:{message}
+Yai! I got new message from stream: {subject}:{message}
+Yai! I got new message from stream: {subject}:{message}
+Yai! I got new message from stream: {subject}:{message}
+```
+
+Check panini app as a client of NATS broker:
+
+```python
+curl http://127.0.0.1:8222/connz
+```
+
+You should get something like:
+
+```python
+{
+  "server_id": "NAKBPTP3AFN3XG4CJBMARGTJPWQS6M6DH2OZ4S5F6MAV4DXEJKXNIVC6",
+  "now": "2021-05-02T22:36:11.940857451Z",
+  "num_connections": 1,
+  "total": 1,
+  "offset": 0,
+  "limit": 1024,
+  "connections": [
+    {
+      "cid": 2,
+      "ip": "192.168.240.21",
+      "port": 35454,
+      "start": "2021-04-27T18:05:31.848989012Z",
+      "last_activity": "2021-05-02T22:36:11.882566724Z",
+      "rtt": "214µs",
+      "uptime": "5d4h30m40s",
+      "idle": "0s",
+      "pending_bytes": 0,
+      "in_msgs": 4,
+      "out_msgs": 4,
+      "in_bytes": 345,
+      "out_bytes": 345,
+      "subscriptions": 1,
+      "name": "quickstart-app__non_docker_env_98846__896705",
+      "lang": "python3",
+      "version": "0.9.2"
+    }
+```
+
+All your microservices under field `connections`
\ No newline at end of file
diff --git a/docs/Quickstart.md b/docs/Quickstart.md
index fa63382..e9aedd7 100644
--- a/docs/Quickstart.md
+++ b/docs/Quickstart.md
@@ -1,153 +1,268 @@
-Panini is a modern framework for fast and straightforward microservices development. It's like fastapi/aiohttp/flask-like solution but for NATS streaming.  The framework allows you to work with NATS features and some additional logic using a simple interface:
-
-- easy to initialize application
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-        service_name='some-microservice-name',
-        host='127.0.0.1',
-        port=4222,
-)
-```
-
-- stream via NATS broker to some subject
-
-```python
-@app.task()
-async def publish():
-    while True:
-        message = get_some_update()
-        await app.publish(subject='some.subject', message=message)
-```
-
-- subscribe to subject
-
-```python
-@app.listen('some.subject')
-async def subject_for_requests_listener(msg):
-    subject = msg.subject
-    message = msg.data
-    # handle incoming message
-```
-
-- request to subject
-
-```python
-response = await app.request(subject='some.request.subject.123', message={'request':'params'})
-```
-
-- receive a request from another microservice and return a response like HTTP request-response
-
-```python
-@app.listen('some.request.subject.123')
-async def request_listener(msg):
-    subject = msg.subject
-    message = msg.data
-    # handle request
-    return {'success': True, 'data': 'request has been processed'}
-```
-
-- create periodic tasks
-
-```python
-@app.timer_task(interval=2)
-async def your_periodic_task():
-    for _ in range(10):
-        await app.publish(subject='some.publish.subject', message={'some':'data'})
-```
-
-- synchronous and asynchronous endpoints
-
-```python
-@app.timer_task(interval=2)
-def your_periodic_task():
-    for _ in range(10):
-        app.publish_sync(subject='some.publish.subject', message={'some':'data'})
-```
-
-- accept different datatypes: dict, str, bytes
-
-```python
-@app.timer_task(interval=2)
-def your_periodic_task():
-    for _ in range(10):
-        app.publish_sync(subject='some.publish.subject', message=b'messageinbytesrequiresminimumoftimetosend', data_type=bytes)
-```
-
-- create middlewares for NATS messages
-
-```python
-from panini.middleware import Middleware
-
-class MyMiddleware(Middleware):
-
-    async def send_publish(self, subject, message, publish_func, **kwargs):
-        print('do something before publish')
-        await publish_func(subject, message, **kwargs)
-        print('do something after publish')
-
-    async def listen_publish(self, msg, cb):
-        print('do something before listen')
-        await cb(msg)
-        print('do something after listen')
-
-    async def send_request(self, subject, message, request_func, **kwargs):
-        print('do something before send request')
-        result = await request_func(subject, message, **kwargs)
-        print('do something after send request')
-        return result
-
-    async def listen_request(self, msg, cb):
-        print('do something before listen request')
-        result = await cb(msg)
-        print('do something after listen request')
-        return result
-```
-
-- create HTTP endpoints with [aiohttp](https://github.com/aio-libs/aiohttp) and NATS endpoints all together in one microservice
-
-```python
-from aiohttp import web
-
-@app.listen('some.publish.subject')
-async def subject_for_requests_listener(msg):
-    handle_incoming_message(msg.subject, msg.data)
-
-@app.http.get('/get')
-async def web_endpoint_listener(request):
-    """
-    Single HTTP endpoint
-    """
-    return web.Response(text="Hello, world")
-
-@app.http.view('/path/to/rest/endpoints')
-class MyView(web.View):
-    """
-    HTTP endpoints for REST schema
-    """
-    async def get(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-
-    async def post(self):
-        request = self.request
-        return web.Response(text="Hello, REST world")
-```
-
-- built-in traffic balancing between instances of the microservice if you have high loads
-
-```python
-app = panini_app.App(
-        service_name='async_publish',
-        host='127.0.0.1',
-        allocation_queue_group='group24', 
-        port=4222,
-)
-
-# incoming traffic will be distributed among 
-# all microservices that are in the "group24"
-```
-
-More ready-to-run examples [here](https://github.com/lwinterface/panini/tree/master/examples)
\ No newline at end of file
+Panini is a modern framework for fast and straightforward microservice development. It's a Flask-like solution but for NATS streaming. 
+
+For this quickstart, we expect the reader to have a minimal experience with:
+
+- Python
+- basic knowledge about NATS
+- FastAPI or Aiohttp or Flask
+
+## A simple listener app example
+
+A minimal app with one stream endpoint, one request endpoint, and one periodic task might look like this:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+        service_name='listener_app',
+        host='127.0.0.1',
+        port=4222,
+)
+
+@app.listen("some.subject.for.request")
+async def request_listener(msg):
+    """ request endpoint """
+    print(f"request {msg.data} from {msg.subject} has been processed")
+    return {"success": True, "message": "request has been processed"}
+
+@app.listen("some.subject.for.stream")
+async def stream_listener(msg):
+    """ stream endpoint """
+    print(f"event {msg.data} from {msg.subject} has been processed")
+
+if __name__ == "__main__":
+    app.start()
+```
+
+What's going on here?
+
+1. Imported Panini.
+2. Initialized app. Created an instance of class App from module panini with any microservice name, NATS host, and port.
+3. First <span class="red">`@app.listen`</span> registers the listening subject <span class="red">`"some.subject.for.request"`</span> with <span class="red">`request_listener`</span>. Every time this app receives a request addressed to <span class="red">`"some.subject.for.request"`</span>, the function <span class="red">`request_listener`</span> is called to process it, then it sends a return response back to an addressee.
+4. Secondly <span class="red">`@app.listen`</span> register the listening subject <span class="red">`"some.subject.for.stream"`</span> with <span class="red">`stream_listener`</span>. Same as with <span class="red">`request_listener`</span> but without sending the result back.
+5. <span class="red">`app.start()`</span> runs an app. No code under this command will ever be called.
+
+Save the above code to file *listener*_*app.py.*
+
+<div class="attention">
+<p class="attention__emoji-icon">💡</p><p> The current function expects only JSON formattable returns, dict or list. However, you can also specify it as string or bytes. More details about this in Datatypes section.</p>
+</div>
+
+Make sure that you have all prerequisites from Install. Open the terminal to run the app:
+
+```python
+> python3 listener_app.py
+======================================================================================
+Panini service connected to NATS..
+id: 3
+name: listener_app__non_docker_env_270377__75017
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+```
+
+That's it. Now let's create something that will generate messages.
+
+## A simple app example that generates messages
+
+Our goal here is to trigger endpoints from listener app above: 
+
+- <span class="purple">"some.subject.for.request"</span>  - request something, receive response
+- <span class="purple">"some.subject.for.stream"</span> - send some event without waiting for response
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+        service_name='sender_app',
+        host='127.0.0.1',
+        port=4222,
+)
+
+@app.task(interval=1)
+async def request_periodically():
+		message = {"data":"request1234567890"}
+    response = await app.request(
+        subject="some.subject.for.request", 
+        message=message,
+    )
+    print(response)
+		
+
+@app.task(interval=1)
+async def publish_periodically():
+		message = {"data":"event1234567890"}
+    await app.publish(
+        subject="some.subject.for.stream", 
+        message=message,
+    )
+
+if __name__ == "__main__":
+    app.start()
+```
+
+What's new here:
+
+1. First, <span class="red">`@app.task`</span> registers function <span class="red">`request_periodically`</span> to call it periodically at given interval, each 1 second in the example.
+2. Function <span class="red">`app.request`</span> sends requests, asynchronously waits for a response.
+3. The second <span class="red">`@app.task`</span> does the same as the first one but for publishing.
+4. Function <span class="red">`app.publish`</span> sends a message like a request but without expecting any response. Fire and forget.
+
+Save the code to new file *sender_app.py.*
+
+Make sure that *listener_app.py* keeps running, then open a new terminal session to run the sender app:
+
+```python
+> python3 sender_app.py
+======================================================================================
+Panini service connected to NATS..
+id: 3
+name: sender_app__non_docker_env_270377__75017
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+{'success': True, 'message': 'request has been processed'}
+```
+
+Note that in the terminal session where you run *listener_app.py* you should see received requests and events:
+
+```python
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+event {'data': 'event1234567890'} from some.subject.for.stream has been processed
+request {'data': 'request1234567890'} from some.subject.for.request has been processed
+```
+
+## More possibilities
+
+In the first example, we created an application that listens for messages, in the second example, an application that sends messages. Panini allows you to freely combine sending and receiving messages in one application. 
+
+Let's check out what else you can do with Panini using a minimal interface:
+
+- One-time tasks on start. Similar to the above periodic task but without <span class="red">`interval`</span> argument
+
+```python
+@app.task()
+async def publish():
+    while True:
+        message = get_some_update()
+        await app.publish(subject='some.subject', message=message)
+```
+
+- Synchronous endpoints
+
+```python
+@app.task(interval=2)
+def your_periodic_task():
+    for _ in range(10):
+        app.publish_sync(
+            subject='some.publish.subject', 
+            message={'some':'data'}
+        )
+```
+
+- Accept different datatypes: dict, str, bytes
+
+```python
+@app.timer_task(interval=2)
+def your_periodic_task():
+    for _ in range(10):
+        app.publish_sync(
+            subject='some.publish.subject', 
+            message=b'messageinbytestosend', 
+            data_type=bytes
+        )
+```
+
+- Create middlewares for NATS messages
+
+```python
+from panini.middleware import Middleware
+
+class MyMiddleware(Middleware):
+
+    async def send_publish(self, subject, message, publish_func, **kwargs):
+        print('do something before publish')
+        await publish_func(subject, message, **kwargs)
+        print('do something after publish')
+
+    async def listen_publish(self, msg, cb):
+        print('do something before listen')
+        await cb(msg)
+        print('do something after listen')
+
+    async def send_request(self, subject, message, request_func, **kwargs):
+        print('do something before send request')
+        result = await request_func(subject, message, **kwargs)
+        print('do something after send request')
+        return result
+
+    async def listen_request(self, msg, cb):
+        print('do something before listen request')
+        result = await cb(msg)
+        print('do something after listen request')
+        return result
+```
+
+- Create HTTP endpoints with [Aiohttp](https://github.com/aio-libs/aiohttp) and NATS endpoints all together in one microservice
+    
+    ```python
+    from aiohttp import web
+    
+    @app.listen('some.publish.subject')
+    async def subject_for_requests_listener(msg):
+        handle_incoming_message(msg.subject, msg.data)
+    
+    @app.http.get('/get')
+    async def web_endpoint_listener(request):
+        """
+        Single HTTP endpoint
+        """
+        return web.Response(text="Hello, world")
+    
+    @app.http.view('/path/to/rest/endpoints')
+    class MyView(web.View):
+        """
+        HTTP endpoints for REST schema
+        """
+        async def get(self):
+            request = self.request
+            return web.Response(text="Hello, REST world")
+    
+        async def post(self):
+            request = self.request
+            return web.Response(text="Hello, REST world")
+    ```
+    
+- Built-in traffic balancing between instances of the microservice if you have high loads
+
+```python
+app = panini_app.App(
+        service_name='async_publish',
+        host='127.0.0.1',
+        allocation_queue_group='group24', 
+        port=4222,
+)
+
+# incoming traffic will be distributed among 
+# all microservices that are in the "group24"
+```
+
+Need more examples? Check [here](https://github.com/lwinterface/panini/tree/master/examples).
\ No newline at end of file
diff --git a/docs/Testing.md b/docs/Testing.md
index aa1a7db..35c7d71 100644
--- a/docs/Testing.md
+++ b/docs/Testing.md
@@ -1,359 +1,382 @@
-#Testing
-Panini testing is possible with various testing frameworks.
-We will show testing based on [pytest](https://docs.pytest.org/) framework.
-
-## Using TestClient
-
-Import `TestClient`.
-
-Create a `TestClient` using [pytest.fixture](https://docs.pytest.org/en/latest/how-to/fixtures.html) with passing to it the function that `runs panini`.
-
-TestClient object `.start()` will start the panini app for testing.
-
-Create `functions` with a name that starts with `test_` (this is standard `pytest` conventions).
-
-Use the `TestClient` object for nats communication the same way as you do with `panini` 
-(but with some limitations)
-
-(The panini uses [nats-python](https://github.com/Gr1N/nats-python) synchronous NATS client for testing)
-
-Write simple `assert` statements with the standard Python expressions that you need to check (`pytest` standard).
-
-```python
-import pytest
-from panini import app as panini_app
-from panini.test_client import TestClient
-
-def run_panini():
-    app = panini_app.App(
-        service_name="test",
-        host="127.0.0.1",
-        port=4222
-    )
-
-    @app.listen("main.subject")
-    async def main_subject(msg):
-        return {"message": "Hello World!"}
-
-    app.start()
-
-@pytest.fixture
-def client():
-    client = TestClient(run_panini).start()
-    yield client
-    client.stop()
-
-def test_main_subject(client):
-    response = client.request("main.subject", {})
-    assert response["message"] == "Hello World!"
-```
-
-Notice that the testing functions are normal `def`, not `async def`.
-And the calls to the client are also normal calls, not using `await`.
-This allows you to use `pytest` directly without complications.
-
-Notice that panini TestClient will run panini app in the `different process`. 
-The Windows and Mac platforms have limitations for transferring objects to different process.
-So we have to use `run_panini` function that will implement or import our app, and we must use `fixtures` to setup TestClient.
-
-Notice that if you use pytest.fixture without `scope` the panini App will setup and teardown for each test.
-If you don't want this - please use `pytest.fixture(scope="module)`
-
-## Separating tests
-
-In a real application, you mostly would have your tests in a different file.
-
-And you **Panini** app can be also in different files or modules.
-
-### Panini **app file**
-
-Let's say you have a file `[main.py](http://main.py)` with your **Panini** app:
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="test",
-    host="127.0.0.1",
-    port=4222
-)
-
-@app.listen("main.subject")
-async def main_subject(msg):
-    return {"message": "Hello World!"}
-
-if __name__ == '__main__':
-    app.start()
-```
-
-### Testing file
-
-Then you could have a file `test_main.py` with your tests, and import your `app` from the `main` module (`main.py`):
-
-```python
-import pytest
-from panini.test_client import TestClient
-
-def run_panini():
-    from .main import app
-    app.start()
-
-@pytest.fixture
-def client():
-    client = TestClient(run_panini).start()
-    yield client
-    client.stop()
-
-def test_main_subject(client):
-    response = client.request("main.subject", {})
-    assert response["message"] == "Hello World!"
-```
-
-## Advanced testing
-
-Now, let's dig into more details to see how to test different parts.
-
-### Error testing
-
-Let's say we need to check that the subject always require the authorization.
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="test",
-    host="127.0.0.1",
-    port=4222
-)
-
-@app.listen("subject.with.authorization")
-async def get_secret(msg):
-    if "authorization" not in msg.data:
-        raise ValueError("You need to be authorized, to get the secret data!")
-    return {"secret": "some meaningful data"}
-
-if __name__ == '__main__':
-    app.start()
-```
-
-Let's create a test for this example
-
-```python
-import pytest
-from panini.test_client import TestClient
-
-def run_panini():
-    from main import app
-    app.start()
-
-@pytest.fixture(scope="module")
-def client():
-    client = TestClient(run_panini).start()
-    yield client
-    client.stop()
-
-def test_secret_subject(client):
-    response = client.request("subject.with.authorization", {"authorization": "token"})
-    assert response["secret"] == "some meaningful data"
-
-def test_unauthorized_secret_subject(client):
-    with pytest.raises(OSError):
-        client.request("subject.with.authorization", {})
-```
-
-We can use this method of testing, but if we look precisely here - it is strange, that we are getting `OSError` instead of `ValueError` which we raised (also the tests will run extremely long because of nats-timeout).
-
- The reason of this, that `TestClient` is a separate `nats` service, that perform in our case simple request, but did not get the response, because of the error on application side.
-
-But still, let's modify a bit our functions, to get the more obvious result:
-
-```python
-@app.listen("subject.with.authorization")
-async def get_secret(msg):
-    if "authorization" not in msg.data:
-        return {"success": False, "message": "You have to be authorized, to get the secret data!"}
-    return {"success": True, "secret": "some meaningful data"}
-```
-
-And the test function will look like this:
-
-```python
-def test_unauthorized_secret_subject(client):
-    response = client.request("subject.with.authorization", {})
-    assert response["success"] is False
-    assert response["message"] == "You have to be authorized, to get the secret data!"
-```
-
-So we get the results faster and in more obvious way.
-
-### Testing microservice with dependencies
-
-Let's say we want to test an **authorization application**, which **depends** on another panini application, that gets data from the DB.
-
-But for our testing purpose, we don't want to create & support the database, because our application does not have direct dependency on DB.
-
-Using `TestClient` we can `mock` the communication between those applications. 
-
-Let's dive into the code:
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="authorization_microservice",
-    host="127.0.0.1",
-    port=4222
-)
-
-@app.listen("get.token")
-async def get_token(msg):
-    if "email" not in msg.data or "password" not in msg.data:
-        return {"message": "You should provide email & password to authorize"}
-    response = await app.request("db.authorization", {"email": msg.data["email"], "password": msg.data["password"]})
-    return {"token": response["db_token"]}
-
-if __name__ == '__main__':
-    app.start()
-```
-
-And then, apply `mocking` using TestClient object `.listen` function.
-
- 
-
-```python
-import pytest
-from panini.test_client import TestClient
-
-def run_panini():
-    from main import app
-    app.start()
-
-fake_db = {"test_email": "test_token"}
-
-@pytest.fixture()
-def client():
-    client = TestClient(run_panini)
-
-    @client.listen("db.authorization")  # mock db.authorization subject
-    def authorize(msg):
-        return {"db_token": fake_db[msg.data["email"]]}  # provide testing data
-
-    client.start()
-    yield client
-    client.stop()
-
-def test_get_token(client):
-    response = client.request("get.token", {"email": "test_email", "password": "test_password"})
-    assert response["token"] == "test_token"
-```
-
-Notice that you have to call `@client.listen` decorator before `client.start()`
-
-### TestClient publish & wait
-
-We **strongly recommend** using other tools for testing (like `client.request` & `client.listen` only), but sometimes we need a specific `client.publish` & `client.wait` functions to test the panini application.
-
-**Example for `client.publish` & `client.wait`**:
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="test",
-    host="127.0.0.1",
-    port=4222
-)
-
-@app.listen("start")
-async def start(msg):
-    await app.request("app.started", {"success": True})
-
-if __name__ == '__main__':
-    app.start()
-```
-
-And testing file:
-
-```python
-import pytest
-from panini.test_client import TestClient
-
-def run_panini():
-    from main import app
-    app.start()
-
-is_app_started = False
-
-@pytest.fixture()
-def client():
-    client = TestClient(run_panini)
-
-    @client.listen("app.started")
-    def app_started(msg):
-        global is_app_started
-        is_app_started = True
-
-    client.start()
-    yield client
-    client.stop()
-
-def test_get_token(client):
-    assert is_app_started is False
-    client.publish("start", {})
-    client.wait(1)  # wait for @client.listen callback to work
-    assert is_app_started is True
-```
-
-Use `client.publish` only, when `@client.listen` returns nothing or **subject**, you want to test returns nothing (but you will need another subject, to check, if the call was successful)
-
-**Example for `client.wait`:**
-
-Let's say you want to test `@app.task` job:
-
-```python
-from panini import app as panini_app
-
-app = panini_app.App(
-    service_name="test",
-    host="127.0.0.1",
-    port=4222
-)
-
-@app.task()
-async def task():
-    await app.publish("task.job", {"job": "test"})
-
-if __name__ == '__main__':
-    app.start()
-```
-
-You can use `@client.listen` and `client.wait` for this stuff:
-
-```python
-import pytest
-from panini.test_client import TestClient
-
-def run_panini():
-    from main import app
-    app.start()
-
-task_data = None
-
-@pytest.fixture()
-def client():
-    client = TestClient(run_panini)
-
-    @client.listen("task.job")
-    def app_started(msg):
-        global task_data
-        task_data = msg.data["job"]
-
-    client.start()
-    yield client
-    client.stop()
-
-def test_get_token(client):
-    assert task_data is None
-    client.wait(1)  # wait for @client.listen callback to work
-    assert task_data == "test"
+Panini testing is possible with various testing frameworks. 
+Here we will show testing based on [pytest](https://docs.pytest.org/) framework.
+
+## Using TestClient
+
+Import <span class="red">`TestClient`</span>.
+
+Create a <span class="red">`TestClient`</span> using [pytest.fixture](https://docs.pytest.org/en/latest/how-to/fixtures.html) by passing it to the function that runs panini.
+
+TestClient object <span class="red">`.start()`</span> will start the panini app for testing.
+
+Create <span class="red">`functions`</span> with a name that starts with <span class="red">`test_`</span> (this is standard <span class="red">`pytest`</span> convention).
+
+Use the <span class="red">`TestClient`</span> object for NATS communication the same way you would do with <span class="red">`panini`</span> 
+
+Panini uses [nats-python](https://github.com/Gr1N/nats-python) synchronous NATS client for testing
+
+Write simple <span class="red">`assert`</span> statements with the standard Python expressions that you need to check (<span class="red">`pytest`</span> standard).
+
+```python
+import pytest
+from panini import app as panini_app
+from panini.test_client import TestClient
+
+def run_panini():
+    app = panini_app.App(
+        service_name="test",
+        host="127.0.0.1",
+        port=4222
+    )
+
+    @app.listen("main.subject")
+    def main_subject(msg):
+        return {"message": "Hello World!"}
+
+    app.start()
+
+@pytest.fixture
+def client():
+    client = TestClient(run_panini).start()
+    yield client
+    client.stop()
+
+def test_main_subject(client):
+    response = client.request("main.subject", {})
+    assert response["message"] == "Hello World!"
+```
+<div class="attention">
+<p class="attention__emoji-icon">🔥</p><p> Notice that the testing functions are normal <span class="red">def</span>, not <span class="red">async def</span>.
+Also, the calls to the client are also normal calls, not using <span class="red">await</span>.
+This allows you to use <span class="red">pytest</span> directly without complications.</p>
+</div>
+
+
+<div class="attention">
+<p class="attention__emoji-icon">🛠</p><p> Notice that panini TestClient will run a panini app in a  <span class="red">different process</span>. 
+The Windows and Mac platforms have limitations for transferring objects to a different process.
+So we have to use  <span class="red">run_panini</span> function that will implement or import our app, and we must use  <span class="red">fixtures</span> to setup TestClient.</p>
+</div>
+<div class="attention">
+<p class="attention__emoji-icon">🔑</p><p>  Notice that if you use <span class="red">pytest.fixture</span> without <span class="red">scope</span> the panini App will setup and teardown for each test.
+If you don't want this - please use <span class="red">pytest.fixture(scope="module)</span></p>
+</div>
+
+
+## Separating tests
+
+In a real application, you mostly would have your tests in a different file.
+
+Your **Panini** app can also be in different files or modules.
+
+### Panini **app file**
+
+Let's say you have a file <span class="red">`main.py`</span> with your **Panini** app:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="test",
+    host="127.0.0.1",
+    port=4222
+)
+
+@app.listen("main.subject")
+def main_subject(msg):
+    return {"message": "Hello World!"}
+
+if __name__ == '__main__':
+    app.start()
+```
+
+### Testing file
+
+Then you could have a file <span class="red">`test_main.py`</span> with your tests, and import your <span class="red">app</span> from the <span class="red">`main`</span> module (<span class="red">`main.py`</span>):
+
+```python
+import pytest
+from panini.test_client import TestClient
+
+def run_panini():
+    from .main import app
+    app.start()
+
+@pytest.fixture
+def client():
+    client = TestClient(run_panini).start()
+    yield client
+    client.stop()
+
+def test_main_subject(client):
+    response = client.request("main.subject", {})
+    assert response["message"] == "Hello World!"
+```
+
+## Advanced testing
+
+Now, let's dig into more details to see how to test different parts.
+<div class="attention">
+<p class="attention__emoji-icon">⚙</p><p> Under the hood, <span class="red">TestClient</span> will run your panini application inside the different process.
+And will communicate with it only using NATS messaging.
+This is how you can <b>simulate</b> your panini app activity.</p>
+</div>
+<div class="attention">
+<p class="attention__emoji-icon">🛠</p><p><span class="red">TestClient</span> will run 2 NATS clients for testing (one for sending, and one for listening).
+The sending NATS client is in the <b>main Thread</b>, while the listening client can be in a separate thread.</p>
+</div>
+
+### Error testing
+
+Let's say we need to check that the subject always requires an authorization.
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="test",
+    host="127.0.0.1",
+    port=4222
+)
+
+@app.listen("subject.with.authorization")
+def get_secret(msg):
+    if "authorization" not in msg.data:
+        raise ValueError("You need to be authorized, to get the secret data!")
+    return {"secret": "some meaningful data"}
+
+if __name__ == '__main__':
+    app.start()
+```
+
+Let's create a test for this example
+
+```python
+import pytest
+from panini.test_client import TestClient
+
+def run_panini():
+    from main import app
+    app.start()
+
+@pytest.fixture(scope="module")
+def client():
+    client = TestClient(run_panini).start()
+    yield client
+    client.stop()
+
+def test_secret_subject(client):
+    response = client.request("subject.with.authorization", {"authorization": "token"})
+    assert response["secret"] == "some meaningful data"
+
+def test_unauthorized_secret_subject(client):
+    with pytest.raises(OSError):
+        client.request("subject.with.authorization", {})
+```
+
+We can use this method of testing, but if we take a better look - it's strange that we are getting a <span class="red">`OSError`</span> instead of a <span class="red">`ValueError`</span> Also, the tests will run for an extremely long time because of nats-timeout.
+
+The reason for this is that <span class="red">`TestClient`</span> is a separate NATS service, that performs a simple request in our case. It does not get the response, because of an error on the application side.
+
+But still, let's modify a bit our functions, to get the more obvious result:
+
+```python
+@app.listen("subject.with.authorization")
+def get_secret(msg):
+    if "authorization" not in msg.data:
+        return {"success": False, "message": "You have to be authorized, to get the secret data!"}
+    return {"success": True, "secret": "some meaningful data"}
+```
+
+And the test function will look like this:
+
+```python
+def test_unauthorized_secret_subject(client):
+    response = client.request("subject.with.authorization", {})
+    assert response["success"] is False
+    assert response["message"] == "You have to be authorized, to get the secret data!"
+```
+
+So we get the results faster and in a more obvious way.
+
+### Testing microservice with dependencies
+
+Let's say we want to test an **authorization application**, which **depends** on another panini application, that fetches data from a DB.
+
+For our testing purpose, we don't want to create & support the database, because our application does not have a direct dependency on DB.
+
+Using <span class="red">`TestClient`</span> we can <span class="red">`mock`</span> the communication between those applications. 
+
+Let's dive into the code:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="authorization_microservice",
+    host="127.0.0.1",
+    port=4222
+)
+
+@app.listen("get.token")
+async def get_token(msg):
+    if "email" not in msg.data or "password" not in msg.data:
+        return {"message": "You should provide email & password to authorize"}
+    response = await app.request("db.authorization", {"email": msg.data["email"], "password": msg.data["password"]})
+    return {"token": response["db_token"]}
+
+if __name__ == '__main__':
+    app.start()
+```
+
+And then, apply <span class="red">`mocking`</span> using <span class="red">`TestClient`</span> object <span class="red">`.listen`</span> function.
+
+ 
+
+```python
+import pytest
+from panini.test_client import TestClient
+
+def run_panini():
+    from main import app
+    app.start()
+
+fake_db = {"test_email": "test_token"}
+
+@pytest.fixture()
+def client():
+    client = TestClient(run_panini)
+
+    @client.listen("db.authorization")  # mock db.authorization subject
+    def authorize(msg):
+        return {"db_token": fake_db[msg.data["email"]]}  # provide testing data
+
+    client.start()
+    yield client
+    client.stop()
+
+def test_get_token(client):
+    response = client.request("get.token", {"email": "test_email", "password": "test_password"})
+    assert response["token"] == "test_token"
+```
+<div class="attention">
+<p class="attention__emoji-icon">💡</p><p>Notice that you have to call <span class="red">@client.listen</span> decorator before <span class="red">client.start()</span></p>
+</div>
+
+### TestClient publish & wait
+
+We **strongly recommend** using other tools for testing (like <span class="red">`client.request`</span> & <span class="red">`client.listen`</span> only), but sometimes we need a specific <span class="red">`client.publish`</span> & <span class="red">`client.wait`</span> to test the panini application.
+<div class="attention">
+<p class="attention__emoji-icon">📖</p><p><span class="red">client.wait</span> is used to manually wait for <span class="red">@client.listen</span> callback to be called.
+It was previously done automatically</p>
+</div>
+
+<div class="attention">
+<p class="attention__emoji-icon">☝</p><p>You should specify <span class="red">client.start(do_always_listen=False)</span> to be able to use <span class="red">client.wait</span></p>
+</div>
+
+**Example for <span class="red">`client.publish`</span> & <span class="red">`client.wait`</span>**:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="test",
+    host="127.0.0.1",
+    port=4222
+)
+
+@app.listen("start")
+async def start(msg):
+    await app.publish("app.started", {"success": True})
+
+if __name__ == '__main__':
+    app.start()
+```
+
+And testing file:
+
+```python
+import pytest
+from panini.test_client import TestClient
+
+def run_panini():
+    from main import app
+    app.start()
+
+is_app_started = False
+
+@pytest.fixture()
+def client():
+    client = TestClient(run_panini)
+
+    @client.listen("app.started")
+    def app_started(msg):
+        global is_app_started
+        is_app_started = True
+
+    client.start(do_always_listen=False)
+    yield client
+    client.stop()
+
+def test_get_token(client):
+    assert is_app_started is False
+    client.publish("start", {})
+    client.wait(1)  # wait for @client.listen callback to work
+    assert is_app_started is True
+```
+<div class="attention">
+<p class="attention__emoji-icon">💡</p><p>Use <span class="red">client.publish</span> only, when <span class="red">@client.listen</span> returns nothing or <b>subject</b>. But you will need another subject, to check, if the call was successful.</p>
+</div>
+
+**Example for <span class="red">`client.wait`</span>:**
+
+Let's say you want to test `@app.task` job:
+
+```python
+from panini import app as panini_app
+
+app = panini_app.App(
+    service_name="test",
+    host="127.0.0.1",
+    port=4222
+)
+
+@app.task()
+async def task():
+    await app.publish("task.job", {"job": "test"})
+
+if __name__ == '__main__':
+    app.start()
+```
+
+You can use `@client.listen` and `client.wait` for this:
+
+```python
+import pytest
+from panini.test_client import TestClient
+
+def run_panini():
+    from main import app
+    app.start()
+
+task_data = None
+
+@pytest.fixture()
+def client():
+    client = TestClient(run_panini)
+
+    @client.listen("task.job")
+    def app_started(msg):
+        global task_data
+        task_data = msg.data["job"]
+
+    client.start(do_always_listen=False)
+    yield client
+    client.stop()
+
+def test_get_token(client):
+    assert task_data is None
+    client.wait(1)  # wait for @client.listen callback to work
+    assert task_data == "test"
 ```
\ No newline at end of file
diff --git a/docs/Validation_for_incoming_data.md b/docs/Validation_for_incoming_data.md
deleted file mode 100644
index 55a72d8..0000000
--- a/docs/Validation_for_incoming_data.md
+++ /dev/null
@@ -1,124 +0,0 @@
-Panini allows to validate fields and data type of values for incoming JSON messages.
-
-Let's say we want to send a message by `microservice_1` ten times per second, message:
-
-```python
-{
-    "key1": "value1",
-    "key2": 2,
-    "key3": 3.024444412342342342,
-    "key4": [1, 2, 3, 4],
-    "key5": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
-    "key6": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
-    "key7": None,
-}
-```
-
-Then receive it by `microservice_2` but validate that all message fields and value types are correct. 
-
-Microservice_1:
-
-```python
-import json
-from panini import  app as panini_app
-
-app = panini_app.App(
-        service_name='microservice_1',
-        host='127.0.0.1',
-        port=4222,
-)
-
-message = {
-    "key1": "value1",
-    "key2": 2,
-    "key3": 3.024444412342342342,
-    "key4": [1, 2, 3, 4],
-    "key5": {"1": 1, "2": 2, "3": 3, "4": 4, "5": 5},
-    "key6": {"subkey1": "1", "subkey2": 2, "3": 3, "4": 4, "5": 5},
-    "key7": None,
-}
-
-@app.timer_task(interval=1)
-async def publish_periodically():
-    for _ in range(10):
-        await app.publish(
-            subject="some.publish.subject", message=message)
-        )
-
-if __name__ == "__main__":
-    app.start()
-```
-
-Microservice_2:
-
-```python
-import json
-from panini import  app as panini_app
-
-app = panini_app.App(
-        service_name='microservice_2',
-        host='127.0.0.1',
-        port=4222,
-)
-
-class SubTestValidator(Validator):
-    subkey1 = Field(type=str)
-    subkey2 = Field(type=int)
-
-class TestValidator(Validator):
-    key1 = Field(type=str)
-    key2 = Field(type=int)
-    key3 = Field(type=float)
-    key4 = Field(type=list)
-    key5 = Field(type=dict)
-    key6 = Field(type=SubTestValidator)
-    key7 = Field(type=int, null=True)
-    key8 = Field(type=int, null=True, default=None)
-
-@app.listen("some.publish.subject", validator=TestValidator)
-async def requests_listener(msg):
-    log.info(f"got message {msg.data}")
-    await asyncio.sleep(1)
-
-if __name__ == "__main__":
-    app.start()
-```
-
-Validator model has to inherit Validator class, accepts python datatypes: 
-
-- str
-- int
-- float
-- list
-- dict
-- bool
-- another validation model(SubTestValidator in example)
-
-Each field additionally: 
-
-- add default key/value if absent in incoming message
-- allows None(null) type
-- allows many expected type's objects in list, for example message:
-
-```python
-{
-    "key1": [
-               {"subkey1": "asdf1", "subkey2": 342},
-               {"subkey1": "sdf1", "subkey2": 2},
-               {"subkey1": "sdfsd1", "subkey2": 2}
-            ]
-}
-```
-
-validators:
-
-```python
-class SubTestValidator(Validator):
-    subkey1 = Field(type=str)
-    subkey2 = Field(type=int)
-
-class TestValidator(Validator):
-    key1 = Field(type=SubTestValidator, many=True) 
-```
-
-If incoming message doesn't pass validation, Panini raise ValidationError
\ No newline at end of file
diff --git a/docs/WebSocket_server.md b/docs/WebSocket_server.md
index 5056086..950b337 100644
--- a/docs/WebSocket_server.md
+++ b/docs/WebSocket_server.md
@@ -1,19 +1,17 @@
-Panini also use [aiohttp] ([https://github.com/aio-libs/aiohttp](https://github.com/aio-libs/aiohttp)) for websockets. Let's see how it works using the example below.
+Panini also uses [Aiohttp](https://github.com/aio-libs/aiohttp) for WebSockets. Let's see how it works using the example below.
 
-Suppose we want to create a microservice that will subscribe to the desired NATS subject and broadcast them to a website in a browser in live mode using websockets. To implement this we need:
+Suppose we want to create a microservice - a gateway between NATS stream and WebSockets stream. It subscribes to the desired NATS subject from a frontend and broadcasts them to a frontend in "live mode". To implement this we need:
 
 - Web interface with web socket connection
 - GET endpoint to upload interface
 - POST endpoint to subscribe/unsubscribe to NATS subjects
 - WSS endpoint to receive live messages
-- Some message generator to test microservice
-- Main logic that's handle subscribe/unsubscribe requests
+- Some NATS message producer to test microservice
+- Main logic that handles subscribe/unsubscribe requests
 
-<написать где взял интерфейс>
+Let's split the app into two modules. One for the main app - <span class="red">`main.py`</span> and another for subscribe/unsubscribe request handler & web page - <span class="red">`handler.py` </span>
 
-Let's split app into two modules. One for main app - `main.py` and another for subscribe/unsubscribe request handler & web page - `handler.py` 
-
-main.py:
+<span class="red">`main.py`</span>:
 
 ```python
 import uuid
@@ -27,10 +25,24 @@ app = panini_app.App(
     service_name="async_NATS_WSS_bridge",
     host='127.0.0.1',
     port=4222,
-    web_server=True,
-    web_port=11111,
 )
-log = app.logger
+app.setup_web_server(port=5001)
+logger = app.logger
+
+async def incoming_messages_callback(subscriber, msg, **kwargs):
+		"""
+		app calls it for each new message from
+		NATS and redirects the message
+		"""
+    try:
+        await subscriber.send_str(
+            json.dumps({"subject": msg.subject, "data": msg.data})
+        )
+    except Exception as e:
+        logger.error(f"error: {str(e)}")
+
+manager = WSSManager(app)
+manager.callback = incoming_messages_callback
 
 test_msg = {
     "key1": "value1",
@@ -42,9 +54,7 @@ test_msg = {
     "key7": None,
 }
 
-manager = WSSManager(app)
-
-@app.timer_task(interval=1)
+@app.task(interval=1)
 async def publish_periodically_for_test():
     test_msg["key3"] = random.random()
     await app.publish("test.subject", test_msg)
@@ -74,7 +84,7 @@ async def web_endpoint_listener(request):
     try:
         await ws.close()
     except Exception as e:
-        log.error(str(e))
+        logger.error(str(e))
     return ws
 
 async def incoming_messages_callback(subscriber, msg, **kwargs):
@@ -87,21 +97,33 @@ async def incoming_messages_callback(subscriber, msg, **kwargs):
             json.dumps({"subject": msg.subject, "data": msg.data})
         )
     except Exception as e:
-        log.error(f"error: {str(e)}")
+        logger.error(f"error: {str(e)}")
 
 if __name__ == "__main__":
-    manager.callback = incoming_messages_callback
     app.http_server.web_app["subscribers"] = {}
     app.start()
 ```
 
-handler.py:
+1. <span class="red">`from handler import WSSManager, html`</span> imports from another module, handler.py
+2. <span class="red">`manager = WSSManager(app)`</span>  initializing of class that handles subscription/unsubscription requests from the users of the frontend
+3. <span class="red">`manager.callback = incoming_messages_callback`</span>  setting callback to incoming NATS messages
+4. <span class="red">`test_msg`</span>  - message for NATS stream
+5. <span class="red">`@app.task(interval=1)`</span>  - function under the decorator publishes messages periodically to subject "test.subject"
+6. <span class="red">`@app.http.get("/")`</span>  - function under the decorator received HTTP request to get main web page
+7. <span class="red">`@app.http.get("/stream")`</span>  - function under the decorator received HTTP request to subscribe user to NATS subject
+8. <span class="red">`app.http_server.web_app["subscribers"] = {}`</span>  - This is where we store subscribers
+
+Let's take a look at handler.py. It includes web page and WebSocket handler
+
+<span class="red">`handler.py`:
 
 ```python
 import json
+import copy
 from panini.utils.logger import get_logger
+from aiohttp.http_websocket import WSMsgType
 
-log = get_logger(None)
+logger = get_logger(None)
 
 html = """
 <!DOCTYPE html>
@@ -118,7 +140,7 @@ html = """
         <ul id='messages'>
         </ul>
         <script>
-            var ws = new WebSocket(`ws://${window.location.hostname}:1111/stream`);
+            var ws = new WebSocket(`ws://${window.location.hostname}:5001/stream`);
             ws.onmessage = function(event) {
                 var messages = document.getElementById('messages')
                 var message = document.createElement('li')
@@ -144,84 +166,142 @@ class WSSManager:
         self.app = app
 
     async def client_listener(self, client_ws_connection, connection_id):
-				"""app calls it for each WSS message from user"""
+        """app calls it for each WSS message from user"""
         while True:
             try:
                 msg = await client_ws_connection.receive()
+                if self.is_close_connection_message(msg):
+                    await self.close_ws_client(client_ws_connection, connection_id)
+                    return client_ws_connection
                 body = json.loads(msg.data)
-                action = body["action"] if "action" in body else "subscribe"
-                if action not in ["subscribe", "unsubscribe"]:
-                    await client_ws_connection.send_str(
-                        json.dumps(
-                            {
-                                "success": False,
-                                "data": "The user has to specify action in message ('subscribe' or 'unsubscribe')",
-                            }
-                        )
-                    )
-                if "subjects" not in body:
-                    raise Exception("subjects required")
+                action = self.get_action(body)
+                await self.validate_ws_message(client_ws_connection, body, action)
                 subjects = body["subjects"]
                 if action == "subscribe":
                     for subject in subjects:
-                        cb = await self._get_callback(client_ws_connection)
-                        ssid = await self.app.aio_subscribe_new_subject(subject, cb)
-                        if subject not in self.ssid_map:
-                            self.ssid_map[subject] = []
-                        self.ssid_map[subject].append(ssid)
-                    await client_ws_connection.send_str(
-                        json.dumps(
-                            {
-                                "success": True,
-                                "data": f"Successfully connected to events: {str(subjects)[1:-1]}",
-                            }
-                        )
+                        cb = await self.get_callback(client_ws_connection)
+                        await self.subscribe(subject, cb)
+                    await self.send_to_ws(
+                        client_ws_connection,
+                        success=True,
+                        message=f"Successfully connected to events: {str(subjects)[1:-1]}"
                     )
                 elif action == "unsubscribe":
                     for subject in subjects:
-                        if subject in self.ssid_map:
-                            for ssid in self.ssid_map[subject]:
-                                await self.app.aio_unsubscribe_ssid(ssid)
-                            await client_ws_connection.send_str(
-                                json.dumps(
-                                    {
-                                        "success": True,
-                                        "data": f"Successfully unsubscribed from event: {subject}",
-                                    }
-                                )
-                            )
-                        else:
-                            await client_ws_connection.send_str(
-                                json.dumps(
-                                    {
-                                        "success": False,
-                                        "data": f"The user did not subscribe to event {subject}",
-                                    }
-                                )
-                            )
+                        await self.unsubscribe(client_ws_connection, subject)
             except Exception as e:
-                log.error(f"WSS error: {str(e)} connection_id {connection_id}")
+                logger.error(f"WSS error: {str(e)} connection_id {connection_id}")
                 try:
-                    await client_ws_connection.send_str(
-                        json.dumps({"success": False, "error": str(e)})
+                    await self.send_to_ws(
+                        client_ws_connection,
+                        success=False,
+                        message=str(e)
                     )
                 except Exception as e:
-                    log.error(str(e), level="error")
+                    logger.error(str(e), level="error")
                 return client_ws_connection
 
-    async def _get_callback(self, subscriber):
-        if hasattr(self, "callback"):
-            cb = self.callback
-        else:
-            raise Exception("self.callback function for incoming messages expected")
+    async def validate_ws_message(self, client_ws_connection, body, action):
+        if action not in ["subscribe", "unsubscribe"]:
+            message = f"The user has to specify action in message ('subscribe' or 'unsubscribe'), got {action} instead"
+            await self.send_to_ws(
+                client_ws_connection,
+                success=False,
+                message=message
+            )
+            raise Exception(message)
+        if "subjects" not in body:
+            raise Exception("subjects required")
+
+    def is_close_connection_message(self, msg):
+        if msg.type == WSMsgType.CLOSE and msg.data in range(1000,1003):
+            return True
 
-        async def wrapper(msg):
-            return await cb(subscriber, msg)
-        return wrapper
+    async def close_ws_client(self, client_ws_connection, conn_id):
+        connections = copy.copy(self.ssid_map)
+        for subject in connections:
+            if conn_id in self.ssid_map[subject]:
+                try:
+                    del self.ssid_map[subject][conn_id]
+                    if self.ssid_map[subject] == {}:
+                        await self.app.unsubscribe_subject(subject)
+                        del self.ssid_map[subject]
+                except Exception as e:
+                    logger.error(str(e))
+        await client_ws_connection.close()
+
+    def get_action(self, body):
+        return body["action"] if "action" in body else "subscribe"
+
+    async def send_to_ws(self, client_ws_connection, success: bool, message: str):
+        message = json.dumps({
+            'success': success,
+            'message': message,
+        })
+        await client_ws_connection.send_str(message)
+
+    async def subscribe(self, subject, cb):
+        ssid = await self.app.subscribe_new_subject(subject, cb)
+        if subject not in self.ssid_map:
+            self.ssid_map[subject] = []
+        self.ssid_map[subject].append(ssid)
+
+    async def unsubscribe(self, client_ws_connection, subject):
+        if not subject in self.ssid_map:
+            await self.send_to_ws(
+                client_ws_connection,
+                success=False,
+                message=f"The user did not subscribe to event {subject}"
+            )
+            return
+        for ssid in self.ssid_map[subject]:
+            await self.app.unsubscribe_ssid(ssid)
+        await self.send_to_ws(
+            client_ws_connection,
+            success=True,
+            message=f"Successfully unsubscribed from event: {subject}"
+        )
+
+    async def get_callback(self, subscriber):
+            if hasattr(self, "callback"):
+                cb = self.callback
+            else:
+                raise Exception("self.callback function for incoming messages expected")
+
+            async def wrapper(msg):
+                return await cb(subscriber, msg)
+            return wrapper
+```
+
+1. <span class="red">`html`</span> - web page html/js code
+2. <span class="red">`WSSManager`</span> - manage every WebSocket request with NATS subject
+
+That's it! Let's run our [main.py](http://main.py) and check [http://127.0.0.1:](http://127.0.0.1:11111)5001:
+
+```python
+> python3 main.p
+======================================================================================
+Panini service connected to NATS..
+id: 5
+name: async_NATS_WSS_bridge__non_docker_env_486358__955463
+
+NATS brokers:
+*  nats://127.0.0.1:4222
+======================================================================================
+
+======== Running on http://0.0.0.0:5001 ========
 ```
 
-That's it! Les't run our [main.py](http://main.py) and check [http://127.0.0.1:11111](http://127.0.0.1:11111) , if everything correct you have to got this page:
+Then you need to follow the link [http://0.0.0.0:5001](http://0.0.0.0:5001/) . If everything is correct you will get this page:
+
+![https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7a4f5637-0c4c-4423-af15-b16170e97399%2FScreenshot_2021-04-29_at_19.18.29.png?table=block&id=86dcd6a6-0a20-49b7-9e65-eb51c7290425&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=2000&userId=&cache=v2](https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7a4f5637-0c4c-4423-af15-b16170e97399%2FScreenshot_2021-04-29_at_19.18.29.png?table=block&id=86dcd6a6-0a20-49b7-9e65-eb51c7290425&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=2000&userId=&cache=v2)
+
+In order to subscribe to NATS subject "test.subject" you need to send a request in expected format:
+
+{"subjects":["test.subject"],"action":"subscribe"}
+
+If everything is correct you should see NATS message on the web page:
 
-![https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7a4f5637-0c4c-4423-af15-b16170e97399%2FScreenshot_2021-04-29_at_19.18.29.png?table=block&id=0c3302e4-3b32-4cb8-a92e-858ddaa67695&width=2560&userId=&cache=v2](https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F7a4f5637-0c4c-4423-af15-b16170e97399%2FScreenshot_2021-04-29_at_19.18.29.png?table=block&id=0c3302e4-3b32-4cb8-a92e-858ddaa67695&width=2560&userId=&cache=v2)
+![Screenshot 2021-11-03 at 14.36.40.png](https://twilight-chord-83a.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Faf0d2eb8-415d-4092-85c1-d7ba77b17251%2FScreenshot_2021-11-03_at_14.36.40.png?table=block&id=cda42be4-4068-42f0-be53-c0e1fcd53664&spaceId=ad8a90bf-1524-4f67-980e-074c3aba664d&width=2000&userId=&cache=v2)
 
-You can also check app below in our example [here](https://github.com/lwinterface/panini/blob/master/examples/simple_examples/async_wss_web_server.py)
\ No newline at end of file
+You can also check this app below in our example [here](https://github.com/lwinterface/panini/blob/master/examples/simple_examples/async_wss_web_server.py).
\ No newline at end of file
diff --git a/docs/What_is_NATS.md b/docs/What_is_NATS.md
index b8653ff..d3d7aa5 100644
--- a/docs/What_is_NATS.md
+++ b/docs/What_is_NATS.md
@@ -1,17 +1,14 @@
-[[NATS.io](http://nats.io/)] ([http://nats.io/](http://nats.io/)) is a high performance messaging system written in Golang. NATS is very easy to use, has very high bandwidth, easily scales. Has 48 well-known clients, 11 of which are supported by the maintainers and 18 are contributors to the community. Delivery guarantees, high availability and fault tolerance. NATS is used on the web, IoT, and blockchains.
-
+The text below is mostly taken from the official [NATS docs](https://docs.nats.io/) 
+[NATS](http://nats.io) is a high-performance messaging system written in Golang. NATS is very easy to use, has very high bandwidth, and easily scales. It has 48 well-known clients, 11 of which are supported by the maintainers. NATS offers delivery guarantees, high availability, and fault tolerance. NATS is used on the web, IoT, and blockchain.
 ### **Subject**
 
-Subject in NATS is like topic in Kafka or url in http server. At its simplest, a subject is just a string of characters that form a name the publisher and subscriber can use to find each other.
-
-Text below is taken from official [NATS docs](https://docs.nats.io/) 
+Subject in NATS is like a topic for Kafka or URL for HTTP. At its simplest, a subject is just a string of characters that form a name that publishers and subscribers can use to find each other.
 
 ### Subject Hierarchies
 
 For example, a world clock application might define the following to logically group related subjects:
 
 ```jsx
-
 time.us
 time.us.east
 time.us.east.atlanta
@@ -21,24 +18,24 @@ time.eu.warsaw
 
 ### Wildcards
 
-NATS provides two *wildcards* that can take the place of one or more elements in a dot-separated subject. Subscribers can use these wildcards to listen to multiple subjects with a single subscription but Publishers will always use a fully specified subject, without the wildcard.
+NATS provides two wildcards that can take the place of one or more elements in a dot-separated subject. Subscribers can use these wildcards to listen to multiple subjects with a single subscription but publishers will use a fully specified subject, without the wildcard.
 
-### Matching A Single Token
+### Matching a single token
 
-The first wildcard is `*` which will match a single token. For example, if an application wanted to listen for eastern time zones, they could subscribe to `time.*.east`, which would match `time.us.east` and `time.eu.east`.
+The first wildcard is <span class="red">`*`</span> which will match a single token. For example, if an application wanted to listen for eastern time zones, they could subscribe to <span class="red">`time.*.east`</span>, which would match <span class="red">`time.us.east`</span> and <span class="red">`time.eu.east`</span>.
 
 ![https://gblobscdn.gitbook.com/assets%2F-LqMYcZML1bsXrN3Ezg0%2F-LqMZac7AGFpQY7ewbGi%2F-LqMZftc5Yt_LEI224RE%2Fsubjects2.svg?alt=media](https://gblobscdn.gitbook.com/assets%2F-LqMYcZML1bsXrN3Ezg0%2F-LqMZac7AGFpQY7ewbGi%2F-LqMZftc5Yt_LEI224RE%2Fsubjects2.svg?alt=media)
 
-### Matching Multiple Tokens
+### Matching multiple tokens
 
-The second wildcard is `>` which will match one or more tokens, and can only appear at the end of the subject. For example, `time.us.>` will match `time.us.east` and `time.us.east.atlanta`, while `time.us.*` would only match `time.us.east` since it can't match more than one token.
+The second wildcard is <span class="red">`>`</span> which will match one or more tokens, and can only appear at the end of the subject. For example, <span class="red">`time.us.>`</span> will match <span class="red">`time.us.east`</span> and <span class="red">`time.us.east.atlanta`</span>, while <span class="red">`time.us.*`</span> would only match <span class="red">`time.us.east`</span> since it can't match more than one token.
 
 ![https://gblobscdn.gitbook.com/assets%2F-LqMYcZML1bsXrN3Ezg0%2F-LqMZac7AGFpQY7ewbGi%2F-LqMZfteO4zWGmIbkFHf%2Fsubjects3.svg?alt=media](https://gblobscdn.gitbook.com/assets%2F-LqMYcZML1bsXrN3Ezg0%2F-LqMZac7AGFpQY7ewbGi%2F-LqMZfteO4zWGmIbkFHf%2Fsubjects3.svg?alt=media)
 
-### Monitoring and Wire Taps
+### Monitoring and wire taps
 
-Subject to your security configuration, wildcards can be used for monitoring by creating something sometimes called a *wire tap*. In the simplest case you can create a subscriber for `>`. This application will receive all messages -- again, subject to security settings -- sent on your NATS cluster.
+Subject to your security configuration, wildcards can be used for monitoring by creating something sometimes called a wire tap. In the simplest case, you can create a subscriber for <span class="red">`>`</span>. This application will receive all messages - again, subject to security settings - sent on your NATS cluster.
 
-### Mix Wildcards
+### Mix wildcards
 
-The wildcard `*` can appear multiple times in the same subject. Both types can be used as well. For example, `*.*.east.>`will receive `time.us.east.atlanta`.
\ No newline at end of file
+The wildcard <span class="red">`*`</span> can appear multiple times in the same subject. Both types can be used as well. For example, <span class="red">`*.*.east.>`</span>will receive <span class="red">`time.us.east.atlanta`<span class="red"></span>.
\ No newline at end of file
diff --git a/docs/assets/logo_p.svg b/docs/assets/logo_p.svg
new file mode 100644
index 0000000..c481184
--- /dev/null
+++ b/docs/assets/logo_p.svg
@@ -0,0 +1,1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#292F33" d="M31 0H5C2.791 0 1 1.791 1 4v28c0 2.209 1.791 4 4 4h26c2.209 0 4-1.791 4-4V4c0-2.209-1.791-4-4-4z"/><path fill="#292F33" d="M14 11c0 2.209-1.791 4-4 4H9c-2.209 0-4-1.791-4-4V9c0-2.209 1.791-4 4-4h1c2.209 0 4 1.791 4 4v2z"/><path fill="#BE1931" d="M22 27c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2v-8c0-1.104.896-2 2-2h15c1.104 0 2 .896 2 2v8zm11-8c0-1.104-.896-2-2-2h-5c-1.104 0-2 .896-2 2v8c0 1.104.896 2 2 2h5c1.104 0 2-.896 2-2v-8zM16 4c0-1.104.896-2 2-2h13c1.104 0 2 .896 2 2v9c0 1.104-.896 2-2 2H18c-1.104 0-2-.896-2-2V4zm-2 9c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2V4c0-1.104.896-2 2-2h7c1.104 0 2 .896 2 2v9z"/><path fill="#DD2E44" d="M14 13c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2V7c0-1.104.896-2 2-2h7c1.104 0 2 .896 2 2v6zm19 0c0 1.104-.896 2-2 2H18c-1.104 0-2-.896-2-2V7c0-1.104.896-2 2-2h13c1.104 0 2 .896 2 2v6z"/><path fill="#E1E8ED" d="M22 27c0 1.104-.896 2-2 2H5c-1.104 0-2-.896-2-2v-5c0-1.104.896-2 2-2h15c1.104 0 2 .896 2 2v5z"/><path fill="#DD2E44" d="M33 27c0 1.104-.896 2-2 2h-5c-1.104 0-2-.896-2-2v-5c0-1.104.896-2 2-2h5c1.104 0 2 .896 2 2v5z"/><path fill="#FFAC33" d="M27.015 9.24c1.315 1.478 1.908 3.098 1.322 3.619l-1.059.941c-.585.522-2.126-.255-3.44-1.733l-3.571-4.013c-1.314-1.479-1.907-3.099-1.321-3.62l1.059-.941c.586-.521 2.124.255 3.438 1.733l3.572 4.014z"/><path fill="#FFD983" d="M22.28 11.076c-.598 1.539-1.477 2.633-1.961 2.444l-.877-.341c-.485-.188-.393-1.588.205-3.126l1.625-4.178c.599-1.538 1.477-2.632 1.962-2.444l.876.341c.485.188.394 1.588-.205 3.126l-1.625 4.178z"/><path fill="#FFD983" d="M23.113 11.536c-1.562.535-2.965.568-3.133.076l-.305-.891c-.169-.492.962-1.324 2.522-1.858l4.242-1.45c1.562-.534 2.964-.567 3.133-.075l.304.89c.169.493-.961 1.325-2.523 1.858l-4.24 1.45z"/><path fill="#5C913B" d="M6.442 7.873c.373-4.106 4.907-.961 7.135 1.049-1.34 1.485-7.586 3.93-7.135-1.049z"/><path fill="#5C913B" d="M6.584 7.784c3.739-1.737 3.293 3.763 2.672 6.698-1.956-.414-7.206-4.59-2.672-6.698z"/><path fill="#77B255" d="M5 8c-1.2-1.6 1-5 6-5 1 1-3 9-6 5z"/><path fill="#B2C2CC" d="M6 27c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l1-1c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-1 1C6.512 26.902 6.256 27 6 27zm12.417.499c-.256 0-.512-.098-.707-.293l-1-1c-.391-.391-.391-1.024 0-1.414.391-.391 1.024-.391 1.415 0l1 1c.391.391.391 1.023 0 1.414-.196.195-.452.293-.708.293zm-6.167-4.291c-.255 0-.511-.098-.707-.292l-.459-.458c-.391-.391-.392-1.023-.002-1.414.389-.392 1.023-.392 1.415-.002l.459.458c.391.391.392 1.023.002 1.414-.195.196-.451.294-.708.294z"/><path fill="#B26304" d="M32.149 23.412c.707.848.592 2.109-.257 2.816l-2.305 1.921c-.848.707-2.109.592-2.816-.257l-1.921-2.305c-.707-.848-.592-2.109.257-2.816l2.305-1.921c.848-.707 2.109-.592 2.816.257l1.921 2.305z"/><path fill="#F4900C" d="M32.149 21.412c.707.848.592 2.109-.257 2.816l-2.305 1.921c-.848.707-2.109.592-2.816-.257l-1.921-2.305c-.707-.848-.592-2.109.257-2.816l2.305-1.921c.848-.707 2.109-.592 2.816.257l1.921 2.305z"/><circle fill="#CCD6DD" cx="12" cy="26" r="1"/><circle fill="#CCD6DD" cx="20" cy="22" r="1"/></svg>
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..776d1ac
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,52 @@
+# Panini documentation
+
+<span style="font-size:4em;">👋</span>
+ 
+**Hey there!**
+
+Welcome to Panini's explanatory documentation! Here you can become familiar with the concept, learn how to setup a working environment, and take your first steps towards building with it.
+
+Panini is a python microframework based on the [nats.py](http://nats.py/) library. Its goal is to offer developers an easy way to create NATS microservices with a lower barrier of entry. It provides a specific template for creating microservices, similarly to FastAPI, Aiohttp, or Flask. Like all of the above frameworks, Panini has its design limits and edge cases. In the case that you become restricted by Panini's capabilities, we recommend switching to [nats.py](https://github.com/nats-io/nats.py).
+
+Panini was inspired by [Faust](https://github.com/robinhood/faust) project.
+
+**What are microservices?**
+
+Microservices are a type of software architecture that breaks up the features of an application into smaller, task-specific functions to make the app more resilient and scalable. More info about it here: [https://microservices.io](https://microservices.io/)
+
+**What can Panini do for me?**
+
+Panini allows you to create microservices that use NATS to communicate with each other and extend the microservice by adding an HTTP server ([aiohttp](https://github.com/aio-libs/aiohttp)) to it if necessary.
+
+**Compatibility with libraries**
+
+Panini works well with various libraries used with FastAPI, Aiohttp, or Flask.
+
+**Can I scale it?**
+
+Yes, you can scale out your microservices horizontally. There are 2 strategies for distributing traffic between microservices:
+
+- Parallel processing: Each microservice processes all messages
+- Balancing: Messages are placed in a common queue and distributed among a group of microservices
+
+**Monitoring**
+
+Panini has a [Grafana](https://grafana.com) dashboard for performance monitoring; the code is [here](https://github.com/lwinterface/panini/blob/master/grafana_dashboard/panini_dashboard.json). Also, we are planning to add the ability to use [Opentracing](https://opentracing.io) soon.
+
+**Do you support JetStream?**
+
+Not at the moment.
+
+**How to start?**
+
+We recommend getting familiar with Panini in the following order:
+
+- Understand what NATS is about
+- Install NATS and Panini
+- Complete Quickstart
+- Understand Panini interface and datatypes
+- Explore validation and middlewares
+- Try to create a Panini microservice with an HTTP server
+- Test and debug
+- Deploy to server
+- Write your own issue and PR to make Panini better 🙂
\ No newline at end of file
diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css
new file mode 100644
index 0000000..e642b65
--- /dev/null
+++ b/docs/stylesheets/extra.css
@@ -0,0 +1,24 @@
+.red{
+    color: #EB5757;
+}
+.red code{
+    color: #EB5757;
+}
+.attention {
+    padding: 5px;
+    background: #80808029;
+    border-radius: 3px;
+    display: flex;
+    margin-bottom: 10px;
+}
+.attention__emoji-icon{
+    margin-right: 10px;
+    margin-left: 10px;
+}
+
+.purple{
+    color: #9065b0;
+}
+.dkGreen{
+    color:#448361;
+}
\ No newline at end of file
diff --git a/examples/emulator/listener_app.py b/examples/emulator/listener_app.py
index 205486e..9f36a51 100644
--- a/examples/emulator/listener_app.py
+++ b/examples/emulator/listener_app.py
@@ -1,8 +1,7 @@
 from panini.app import App
 
 from panini.middleware.reader_emulator_middleware import ReaderEmulatorMiddleware
-from panini.middleware.writer_emulator_middleware import WriterEmulatorMiddleware
-from panini.nats_client import Msg
+from panini.managers.nats_client import Msg
 
 app = App(service_name="listener", host="127.0.0.1", port=4222)
 
diff --git a/examples/simple_examples/_wss_manager.py b/examples/simple_examples/_wss_manager.py
index d5a5477..aa3ce8e 100644
--- a/examples/simple_examples/_wss_manager.py
+++ b/examples/simple_examples/_wss_manager.py
@@ -1,7 +1,9 @@
 import json
+import copy
 from panini.utils.logger import get_logger
+from aiohttp.http_websocket import WSMsgType
 
-log = get_logger(None)
+logger = get_logger(None)
 
 html = """
 <!DOCTYPE html>
@@ -18,7 +20,7 @@
         <ul id='messages'>
         </ul>
         <script>
-            var ws = new WebSocket(`ws://${window.location.hostname}:1111/stream`);
+            var ws = new WebSocket(`ws://${window.location.hostname}:5001/stream`);
             ws.onmessage = function(event) {
                 var messages = document.getElementById('messages')
                 var message = document.createElement('li')
@@ -45,77 +47,108 @@ def __init__(self, app):
         self.app = app
 
     async def client_listener(self, client_ws_connection, connection_id):
+        """app calls it for each WSS message from user"""
         while True:
             try:
                 msg = await client_ws_connection.receive()
+                if self.is_close_connection_message(msg):
+                    await self.close_ws_client(client_ws_connection, connection_id)
+                    return client_ws_connection
                 body = json.loads(msg.data)
-                action = body["action"] if "action" in body else "subscribe"
-                if action not in ["subscribe", "unsubscribe"]:
-                    await client_ws_connection.send_str(
-                        json.dumps(
-                            {
-                                "success": False,
-                                "data": "The user has to specify action in message ('subscribe' or 'unsubscribe')",
-                            }
-                        )
-                    )
-                if "subjects" not in body:
-                    raise Exception("subjects required")
+                action = self.get_action(body)
+                await self.validate_ws_message(client_ws_connection, body, action)
                 subjects = body["subjects"]
                 if action == "subscribe":
                     for subject in subjects:
-                        cb = await self._get_callback(client_ws_connection)
-                        ssid = await self.app.subscribe_new_subject(subject, cb)
-                        if subject not in self.ssid_map:
-                            self.ssid_map[subject] = []
-                        self.ssid_map[subject].append(ssid)
-                    await client_ws_connection.send_str(
-                        json.dumps(
-                            {
-                                "success": True,
-                                "data": f"Successfully connected to events: {str(subjects)[1:-1]}",
-                            }
-                        )
+                        cb = await self.get_callback(client_ws_connection)
+                        await self.subscribe(subject, cb)
+                    await self.send_to_ws(
+                        client_ws_connection,
+                        success=True,
+                        message=f"Successfully connected to events: {str(subjects)[1:-1]}"
                     )
                 elif action == "unsubscribe":
                     for subject in subjects:
-                        if subject in self.ssid_map:
-                            for ssid in self.ssid_map[subject]:
-                                await self.app.unsubscribe_ssid(ssid)
-                            await client_ws_connection.send_str(
-                                json.dumps(
-                                    {
-                                        "success": True,
-                                        "data": f"Successfully unsubscribed from event: {subject}",
-                                    }
-                                )
-                            )
-                        else:
-                            await client_ws_connection.send_str(
-                                json.dumps(
-                                    {
-                                        "success": False,
-                                        "data": f"The user did not subscribe to event {subject}",
-                                    }
-                                )
-                            )
+                        await self.unsubscribe(client_ws_connection, subject)
             except Exception as e:
-                log.error(f"WSS error: {str(e)} connection_id {connection_id}")
+                logger.error(f"WSS error: {str(e)} connection_id {connection_id}")
                 try:
-                    await client_ws_connection.send_str(
-                        json.dumps({"success": False, "error": str(e)})
+                    await self.send_to_ws(
+                        client_ws_connection,
+                        success=False,
+                        message=str(e)
                     )
                 except Exception as e:
-                    log.error(str(e), level="error")
+                    logger.error(str(e), level="error")
                 return client_ws_connection
 
-    async def _get_callback(self, subscriber):
-        if hasattr(self, "callback"):
-            cb = self.callback
-        else:
-            raise Exception("self.callback function for incoming messages expected")
+    async def validate_ws_message(self, client_ws_connection, body, action):
+        if action not in ["subscribe", "unsubscribe"]:
+            message = f"The user has to specify action in message ('subscribe' or 'unsubscribe'), got {action} instead"
+            await self.send_to_ws(
+                client_ws_connection,
+                success=False,
+                message=message
+            )
+            raise Exception(message)
+        if "subjects" not in body:
+            raise Exception("subjects required")
+
+    def is_close_connection_message(self, msg):
+        if msg.type == WSMsgType.CLOSE and msg.data in range(1000,1003):
+            return True
+
+    async def close_ws_client(self, client_ws_connection, conn_id):
+        connections = copy.copy(self.ssid_map)
+        for subject in connections:
+            if conn_id in self.ssid_map[subject]:
+                try:
+                    del self.ssid_map[subject][conn_id]
+                    if self.ssid_map[subject] == {}:
+                        await self.app.unsubscribe_subject(subject)
+                        del self.ssid_map[subject]
+                except Exception as e:
+                    logger.error(str(e))
+        await client_ws_connection.close()
+
+    def get_action(self, body):
+        return body["action"] if "action" in body else "subscribe"
+
+    async def send_to_ws(self, client_ws_connection, success: bool, message: str):
+        message = json.dumps({
+            'success': success,
+            'message': message,
+        })
+        await client_ws_connection.send_str(message)
+
+    async def subscribe(self, subject, cb):
+        ssid = await self.app.subscribe_new_subject(subject, cb)
+        if subject not in self.ssid_map:
+            self.ssid_map[subject] = []
+        self.ssid_map[subject].append(ssid)
+
+    async def unsubscribe(self, client_ws_connection, subject):
+        if not subject in self.ssid_map:
+            await self.send_to_ws(
+                client_ws_connection,
+                success=False,
+                message=f"The user did not subscribe to event {subject}"
+            )
+            return
+        for ssid in self.ssid_map[subject]:
+            await self.app.unsubscribe_ssid(ssid)
+        await self.send_to_ws(
+            client_ws_connection,
+            success=True,
+            message=f"Successfully unsubscribed from event: {subject}"
+        )
 
-        async def wrapper(msg):
-            return await cb(subscriber, msg)
+    async def get_callback(self, subscriber):
+            if hasattr(self, "callback"):
+                cb = self.callback
+            else:
+                raise Exception("self.callback function for incoming messages expected")
 
-        return wrapper
+            async def wrapper(msg):
+                return await cb(subscriber, msg)
+            return wrapper
\ No newline at end of file
diff --git a/examples/simple_examples/async_middleware.py b/examples/simple_examples/async_middleware.py
index b15748b..29e8ca9 100644
--- a/examples/simple_examples/async_middleware.py
+++ b/examples/simple_examples/async_middleware.py
@@ -28,10 +28,10 @@ async def publish():
         log.info(f"send message {message}")
 
 
-@app.timer_task(interval=2)
-async def request_periodically():
-    response = await app.request(subject="some.request.subject", message=message)
-    log.info(f"response message from periodic task: {response}")
+# @app.task(interval=2)
+# async def request_periodically():
+#     response = await app.request(subject="some.request.subject", message=message)
+#     log.info(f"response message from periodic task: {response}")
 
 
 @app.listen("some.publish.subject")
diff --git a/examples/simple_examples/async_publish.py b/examples/simple_examples/async_publish.py
index 4cb9fd1..8014bbd 100644
--- a/examples/simple_examples/async_publish.py
+++ b/examples/simple_examples/async_publish.py
@@ -22,14 +22,14 @@
 
 @app.task()
 async def publish():
-    for _ in range(10):
+    for _ in range(1):
         await app.publish(subject="some.publish.subject", message=message)
         log.info(f"send message {message}")
 
 
-@app.timer_task(interval=2)
+@app.task(interval=2)
 async def publish_periodically():
-    for _ in range(10):
+    for _ in range(1):
         await app.publish(subject="some.publish.subject", message=message)
         log.info(f"send message from periodic task {message}")
 
diff --git a/examples/simple_examples/async_publish_from_another_thread.py b/examples/simple_examples/async_publish_from_another_thread.py
index 33e4566..bb905aa 100644
--- a/examples/simple_examples/async_publish_from_another_thread.py
+++ b/examples/simple_examples/async_publish_from_another_thread.py
@@ -27,7 +27,7 @@ async def publish_periodically():
 
 def another_thread_func():
     for _ in range(10):
-        app.connector.publish_from_another_thread(
+        app.nats.publish_from_another_thread(
             subject="some.publish.subject", message=message
         )
 
diff --git a/examples/simple_examples/async_web_server.py b/examples/simple_examples/async_web_server.py
index 9cb0524..0014b64 100644
--- a/examples/simple_examples/async_web_server.py
+++ b/examples/simple_examples/async_web_server.py
@@ -5,9 +5,9 @@
     service_name="async_web_server",
     host="127.0.0.1",
     port=4222,
-    web_server=True,
 )
 
+app.setup_web_server()
 log = app.logger
 
 message = {
diff --git a/examples/simple_examples/async_web_server_with_periodic_task.py b/examples/simple_examples/async_web_server_with_periodic_task.py
new file mode 100644
index 0000000..5fcff09
--- /dev/null
+++ b/examples/simple_examples/async_web_server_with_periodic_task.py
@@ -0,0 +1,53 @@
+__version__ = '0.1.0'
+from aiohttp import web
+from panini import app as panini_app
+
+
+
+app = panini_app.App(
+    service_name="async_web_server_with_periodic_task",
+    host='127.0.0.1',
+    port=4222,
+)
+app.setup_web_server(port=8081)
+log = app.logger
+NUM = 0
+
+def get_message():
+    return {
+        "version": __version__,
+        "name": app.nats.client_id,
+        "id": app.nats.client.client_id,
+        "NATS_brokers": app.nats.servers,
+}
+
+
+@app.task()
+async def publish():
+    message = {"event": "app started!"}
+    await app.publish(subject="some.request.subject", message=message)
+    log.info(str(message))
+
+@app.timer_task(interval=2)
+async def publish_periodically():
+    subject = "test.app1.stream"
+    global NUM
+    NUM+=1
+    message = get_message()
+    message['counter'] = NUM
+    await app.publish(subject=subject, message=message)
+    log.info(f"sent {message}")
+
+@app.http.get("/profile")
+async def web_endpoint_listener(request):
+    """
+    Single HTTP endpoint
+    """
+    message = get_message()
+    message['counter'] = NUM
+    return web.Response(text=str(message))
+
+
+
+if __name__ == "__main__":
+    app.start()
diff --git a/examples/simple_examples/async_wss_web_server.py b/examples/simple_examples/async_wss_web_server.py
index e44c8a4..a91c818 100644
--- a/examples/simple_examples/async_wss_web_server.py
+++ b/examples/simple_examples/async_wss_web_server.py
@@ -3,18 +3,31 @@
 import json
 from aiohttp import web
 from panini import app as panini_app
-from examples.simple_examples._wss_manager import WSSManager, html
-
+from _wss_manager import WSSManager, html
 
 app = panini_app.App(
     service_name="async_NATS_WSS_bridge",
-    # host='nats-server' if 'HOSTNAME' in os.environ else '127.0.0.1',
-    host="54.36.108.188",
+    host='127.0.0.1',
     port=4222,
-    web_server=True,
-    web_port=1111,
 )
-log = app.logger
+app.setup_web_server(port=5001)
+logger = app.logger
+
+async def incoming_messages_callback(subscriber, msg, **kwargs):
+    """
+    app calls it for each new message from
+    NATS and redirects the message
+    """
+    try:
+        await subscriber.send_str(
+            json.dumps({"subject": msg.subject, "data": msg.data})
+        )
+    except Exception as e:
+        logger.error(f"error: {str(e)}")
+
+manager = WSSManager(app)
+manager.callback = incoming_messages_callback
+
 test_msg = {
     "key1": "value1",
     "key2": 2,
@@ -25,10 +38,7 @@
     "key7": None,
 }
 
-manager = WSSManager(app)
-
-
-@app.timer_task(interval=1)
+@app.task(interval=1)
 async def publish_periodically_for_test():
     test_msg["key3"] = random.random()
     await app.publish("test.subject", test_msg)
@@ -51,6 +61,7 @@ async def web_endpoint_listener(request):
 
 @app.http.get("/stream")
 async def web_endpoint_listener(request):
+    """WebSocket connection """
     ws = web.WebSocketResponse()
     await ws.prepare(request)
     connection_id = str(uuid.uuid4())[:10]
@@ -59,20 +70,23 @@ async def web_endpoint_listener(request):
     try:
         await ws.close()
     except Exception as e:
-        log.error(str(e))
+        logger.error(str(e))
     return ws
 
 
 async def incoming_messages_callback(subscriber, msg, **kwargs):
+    """
+    app calls it for each new message from
+    NATS and redirects the message
+    """
     try:
         await subscriber.send_str(
             json.dumps({"subject": msg.subject, "data": msg.data})
         )
     except Exception as e:
-        log.error(f"error: {str(e)}")
+        logger.error(f"error: {str(e)}")
 
 
 if __name__ == "__main__":
-    manager.callback = incoming_messages_callback
     app.http_server.web_app["subscribers"] = {}
     app.start()
diff --git a/mkdocs.yml b/mkdocs.yml
index dcb58bd..67a5b36 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -2,43 +2,44 @@ site_name: Panini
 repo_url: https://github.com/lwinterface/panini
 repo_name: lwinterface/panini
 nav:
-    - Install: Install.md
-    - Quickstart: Quickstart.md
-    - What is NATS?: What_is_NATS.md
-    - How Panini works?: 
-        - First Step: How_first_step.md
-        - Arguments panini app takes: How_arguments.md
-        - Main functions of panini app : How_main_func.md
-        - Incoming message strucutre: How_incoming_msg.md
-    - Datatypes: Datatypes.md
-    - Validation for incoming data: Validation_for_incoming_data.md
-    - Middlewares: Middlewares.md
-    - HTTP server: HTTP_server.md
-    - WebSocket server: WebSocket_server.md
-    - Balancer: Balancer.md
-    - Middlewares: Middlewares.md
-    - Monitoring application: Monitoring_application.md
-    - Emulator - "time machine" debug: Emulator.md
-    - Distributed tracing: Distributed_tracing.md
-    - Deployment: Deployment.md
-    - Changelog: Changelog.md
-    - License: License.md
-    - How to contribute: How_to_contribute.md
-    - Testing: Testing.md
-    - AsyncTesting: AsyncTesting.md
-    - Logger: Logger.md
-    - Examples: Examples.md
+    - 🏰 What is NATS?: What_is_NATS.md
+    - 🚚 Install: Install.md
+    - 🍳 Quickstart: Quickstart.md
+    - 👩‍🎓 Panini interface: 
+        - App Arguments: Panini_App_Arguments.md
+        - App Functions: Panini_App_Functions.md
+        - Incoming message structure: Incoming_Message_Structure.md
+        - How to validate that microservice connected to NATS: Panini_Validation.md
+    - 👨‍👩‍👧‍👦 Datatypes: Datatypes.md
+    - 🔑 Authentication: Authentication.md
+    - 👮‍♂️ Incoming data validation: Data_Validation.md
+    - 🖖 Middlewares: Middlewares.md
+    - 🦆 HTTP server: HTTP_server.md
+    - 🦅 WebSocket server example: WebSocket_server.md
+    - 🤹 Balancer: Balancer.md
+    - 👀 Application Monitoring : Application_monitoring.md
+    - 🕰️ Emulator - "time machine" debug: Emulator.md
+    - 🔍 Distributed tracing: Distributed_tracing.md
+    - 🏗️ Deployment: Deployment.md
+    - 📋 Changelog: Changelog.md
+    - 🗽 License: License.md
+    - 👐 How to contribute: How_to_contribute.md
+    - 🔧 Testing: Testing.md
+    - 📋 Logger: Logger.md
 plugins:
   - search:
       separator: '[\s\-\.]+'
       lang: en    
 theme:
   name: material
+  logo: assets/logo_p.svg
   features:
    - search.suggest
    - search.highlight
   palette:
     primary: light blue
+extra_css:
+  - stylesheets/extra.css
 markdown_extensions:
   - admonition
   - abbr
diff --git a/panini/app.py b/panini/app.py
index b7999bf..431d7cc 100644
--- a/panini/app.py
+++ b/panini/app.py
@@ -1,121 +1,92 @@
-import os
-import time
 import asyncio
-import uuid
 import logging
+import os
+import typing
+import uuid
+from types import CoroutineType
 
 from aiohttp import web
 
-from panini.nats_client import NATSClient
-from .managers import (
-    _EventManager,
-    _TaskManager,
-    _IntervalTaskManager,
-    _MiddlewareManager,
-)
+from panini.managers.nats_client import NATSClient
+from .exceptions import InitializingEventManagerError
+
 from .http_server.http_server_app import HTTPServer
-from .exceptions import (
-    InitializingEventManagerError,
-    InitializingTaskError,
-    InitializingIntervalTaskError,
-)
+from .managers.event_manager import EventManager
+from .managers.task_manager import TaskManager
 from .middleware.error import ErrorMiddleware
+from .utils import logger
 from .utils.helper import (
-    start_thread,
     get_app_root_path,
     create_client_code_by_hostname,
 )
-from .utils import logger
 
 _app = None
 
 
-class App(
-    _EventManager, _TaskManager, _IntervalTaskManager, _MiddlewareManager, NATSClient
-):
+class App:
     def __init__(
-        self,
-        host: str,
-        port: int,
-        service_name: str = "panini_microservice_" + str(uuid.uuid4())[:10],
-        client_id: str = None,
-        tasks: list = None,
-        reconnect: bool = False,
-        max_reconnect_attempts: int = 60,
-        reconnecting_time_sleep: int = 2,
-        subscribe_subjects_and_callbacks: dict = None,
-        allocation_queue_group: str = "",
-        listen_subject_only_if_include: list = None,
-        listen_subject_only_if_exclude: list = None,
-        web_server: bool = False,
-        web_app: web.Application = None,
-        web_host: str = None,
-        web_port: int = None,
-        logger_required: bool = True,
-        logger_files_path: str = None,
-        logger_in_separate_process: bool = False,
-        pending_bytes_limit=65536 * 1024 * 10,
+            self,
+            host: str = None,
+            port: int or str = None,
+            service_name: str = "panini_microservice_" + str(uuid.uuid4())[:10],
+            servers: list = None,
+            client_id: str = None,
+            reconnect: bool = False,
+            max_reconnect_attempts: int = 60,
+            reconnecting_time_sleep: int = 2,
+            allocation_queue_group: str = "",
+
+            logger_required: bool = True,
+            logger_files_path: str = None,
+            logger_in_separate_process: bool = False,
+            pending_bytes_limit=65536 * 1024 * 10,
+            **kwargs
     ):
         """
         :param host: NATS broker host
         :param port: NATS broker port
         :param service_name: Name of microservice
+        :param servers: Alternative to NATS broker host+NATS broker port. Allowed establish connection to multiple NATS brokers
         :param client_id: id of microservice, name and client_id used for NATS client name generating
-        :param tasks: List of additional tasks
         :param reconnect: allows reconnect if connection to NATS has been lost
         :param max_reconnect_attempts: number of reconnect attempts
         :param reconnecting_time_sleep: pause between reconnection
-        :param subscribe_subjects_and_callbacks: if you need to subscribe additional
-                                        subjects(except subjects from event.py).
-                                        This way doesn't support validators
         :param allocation_queue_group: name of NATS queue for distributing incoming messages among many NATS clients
                                     more detailed here: https://docs.nats.io/nats-concepts/queue
-        :param listen_subject_only_if_include:   if not None, client will subscribe
-                                                only to subjects that include these key words
-        :param listen_subject_only_if_exclude:   if not None, client will not subscribe
-                                                 to subjects that include these key words
-        :param web_app: web.Application:       custom aiohttp app that you can create separately from panini.
-                            if you set this argument client will only run this aiohttp app without handling
-        :param web_host: Web application host
-        :param web_port: Web application port
         :param logger_required: Is logger required for the project (if not - EmptyLogger will be provided)
         :param logger_files_path: main path for logs
         :param logger_in_separate_process: use log in the same or in different process
         """
-        if subscribe_subjects_and_callbacks is None:
-            subscribe_subjects_and_callbacks = {}
-        if tasks is None:
-            tasks = []
+
         try:
+            # client_id initialization
             if client_id is None:
-                client_id = create_client_code_by_hostname(service_name)
+                self.client_id = create_client_code_by_hostname(service_name)
             else:
-                client_id = client_id
-            os.environ["CLIENT_ID"] = client_id
-            self.client_id = client_id
+                self.client_id = client_id
+            os.environ["CLIENT_ID"] = self.client_id
+
+            self.loop = asyncio.get_event_loop()
+
             self.service_name = service_name
-            self.nats_config = {
-                "host": host,
-                "port": port,
-                "client_id": client_id,
-                "listen_subjects_callbacks": None,
-                "allow_reconnect": reconnect,
-                "queue": allocation_queue_group,
-                "max_reconnect_attempts": max_reconnect_attempts,
-                "reconnecting_time_wait": reconnecting_time_sleep,
-                "pending_bytes_limit": pending_bytes_limit,
-            }
-            self.tasks = tasks
-            self.listen_subject_only_if_include = listen_subject_only_if_include
-            self.listen_subject_only_if_exclude = listen_subject_only_if_exclude
-            assert (
-                self.listen_subject_only_if_include is None
-                or self.listen_subject_only_if_exclude is None
-            ), "You can use only 1 of listen_subject_only_if_include/exclude at the same moment!"
-            self.subscribe_subjects_and_callbacks = subscribe_subjects_and_callbacks
 
-            self.app_root_path = get_app_root_path()
+            self._event_manager = EventManager()
+            self._task_manager = TaskManager()
+            self.nats = NATSClient(
+                host=host,
+                port=port,
+                servers=servers,
+                client_id=self.client_id,
+                loop=self.loop,
+                allow_reconnect=reconnect,
+                queue=allocation_queue_group,
+                max_reconnect_attempts=max_reconnect_attempts,
+                reconnecting_time_wait=reconnecting_time_sleep,
+                pending_bytes_limit=pending_bytes_limit,
+                **kwargs
+            )
 
+            self.app_root_path = get_app_root_path()
             self.logger_required = logger_required
             self.logger_in_separate_process = logger_in_separate_process
 
@@ -144,43 +115,48 @@ def __init__(
             self.log_listener_queue = None
             self.change_logger_config_listener_queue = None
 
-            if web_server:
-                self.http = web.RouteTableDef()  # for http decorator
-                if web_app:
-                    self.http_server = HTTPServer(base_app=self, web_app=web_app)
-                else:
-                    self.http_server = HTTPServer(
-                        base_app=self, host=web_host, port=web_port
-                    )
-            else:
-                self.http_server = None
+            self.http_server = None
+            self.http = None
+
+            self.task = self._task_manager.register_task
+            self.timer_task = self._task_manager.register_interval_task
+
             global _app
             _app = self
+
         except InitializingEventManagerError as e:
             error = f"App.event_registrar critical error: {str(e)}"
             raise InitializingEventManagerError(error)
 
-    # TODO: implement change logging configuration during runtime to make it work properly
-    def change_log_config(self, new_formatters: dict, new_handlers: dict):
-        self.change_logger_config_listener_queue.put(
-            {"new_formatters": new_formatters, "new_handlers": new_handlers}
-        )
-        raise NotImplementedError
+    def setup_web_server(self, host=None, port=None, web_app=None, params: dict = None):
+        """
+        Setup server and run with NATS when called app.start()
+        """
+        self.http = web.RouteTableDef()  # for http decorator
+        if web_app:
+            self.http_server = HTTPServer(routes=self.http, loop=self.loop, web_app=web_app, web_server_params=params)
+        else:
+            self.http_server = HTTPServer(routes=self.http, loop=self.loop, host=host, port=port, web_server_params=params)
+
+    def add_filters(self, include: list = None, exclude: list = None):
+        """
+        Allows to listen subject only if subject include or exclude string from lists in agrs
+        """
+        return self.nats.add_filters(include, exclude)
 
     def set_logger(
-        self,
-        service_name,
-        app_root_path,
-        logger_files_path,
-        in_separate_process,
-        client_id,
+            self,
+            service_name,
+            app_root_path,
+            logger_files_path,
+            in_separate_process,
+            client_id
     ):
         if in_separate_process:
             (
                 self.log_listener_queue,
                 self.log_stop_event,
-                self.logger_process,
-                self.change_logger_config_listener_queue,
+                self.logger_process
             ) = logger.set_logger(
                 service_name,
                 app_root_path,
@@ -198,13 +174,96 @@ def set_logger(
             )
         self.logger.logger = logging.getLogger(service_name)
 
+    def listen(
+            self,
+            subject: list or str,
+            validator: type = None,
+            data_type="json.loads"
+    ):
+        return self._event_manager.listen(subject, validator, data_type)
+
+    async def publish(
+            self,
+            subject: str,
+            message,
+            reply_to: str = None,
+            force: bool = False,
+            data_type: type or str = "json.dumps"
+    ):
+        return await self.nats.publish(subject, message, reply_to, force, data_type)
+
+    def publish_sync(
+            self,
+            subject: str,
+            message,
+            reply_to: str = None,
+            force: bool = False,
+            data_type: type or str = "json.dumps",
+    ):
+        return self.nats.publish_sync(subject, message, reply_to, force, data_type)
+
+    async def request(
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
+            data_type: type or str = "json.dumps",
+            callback: typing.Callable = None
+    ):
+        return await self.nats.request(subject, message, timeout, data_type, callback)
+
+    def request_sync(
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
+            data_type: type or str = "json.dumps",
+            callback: typing.Callable = None,
+    ):
+        return self.nats.request_sync(subject, message, timeout, data_type, callback)
+
+    def subscribe_new_subject_sync(self, subject: str, callback: CoroutineType, **kwargs):
+        return self.nats.subscribe_new_subject_sync(subject, callback, **kwargs)
+
+    async def subscribe_new_subject(
+            self,
+            subject: str,
+            callback: CoroutineType,
+            init_subscription=False,
+            is_async=False,
+            data_type=None
+    ):
+        return await self.nats.subscribe_new_subject(subject, callback, init_subscription, is_async, data_type)
+
+    def unsubscribe_subject_sync(self, subject: str):
+        return self.nats.unsubscribe_subject_sync(subject)
+
+    async def unsubscribe_subject(self, subject: str):
+        return await self.nats.unsubscribe_subject(subject)
+
+    def unsubscribe_ssid_sync(self, ssid: int, subject: str = None):
+        return self.nats.unsubscribe_ssid_sync(ssid, subject)
+
+    async def unsubscribe_ssid(self, ssid: int, subject: str = None):
+        return await self.nats.unsubscribe_ssid(ssid, subject)
+
+    def add_middleware(self, cls, *args, **kwargs):
+        return self.nats.middleware_manager.add_middleware(cls, *args, **kwargs)
+
+    def _start_event(self):
+        asyncio.ensure_future(
+            self.nats.client.publish(
+                f"panini_events.{self.service_name}.{self.client_id}.started",
+                b"{}",
+            )
+        )
+
     def start(self):
         if (
-            os.environ.get("PANINI_TEST_MODE")
-            and os.environ.get("PANINI_TEST_MODE_USE_ERROR_MIDDLEWARE", "false")
-            == "true"
+                os.environ.get("PANINI_TEST_MODE")
+                and os.environ.get("PANINI_TEST_MODE_USE_ERROR_MIDDLEWARE", "false")
+                == "true"
         ):
-
             def exception_handler(e, **kwargs):
                 self.logger.exception(f"Error: {e}, for kwargs: {kwargs}")
                 raise
@@ -221,62 +280,21 @@ def exception_handler(e, **kwargs):
                 self.client_id,
             )
 
-        try:
-            subjects_and_callbacks = self.SUBSCRIPTIONS
-            subjects_and_callbacks.update(self.subscribe_subjects_and_callbacks)
-            if self.listen_subject_only_if_include is not None:
-                for subject in subjects_and_callbacks.copy():
-                    success = False
-                    for subject_include in self.listen_subject_only_if_include:
-                        if subject_include in subject:
-                            success = True
-                            break
-                    if success is False:
-                        del subjects_and_callbacks[subject]
-            if self.listen_subject_only_if_exclude is not None:
-                for subject in subjects_and_callbacks.copy():
-                    success = True
-                    for subject_exclude in self.listen_subject_only_if_exclude:
-                        if subject_exclude in subject:
-                            success = False
-                            break
-                    if success is False:
-                        del subjects_and_callbacks[subject]
-        except InitializingEventManagerError as e:
-            error = f"App.event_registrar critical error: {str(e)}"
-            raise InitializingEventManagerError(error)
+        self.nats.set_listen_subjects_callbacks(self._event_manager.subscriptions)
+        self.nats.start()
 
-        self.nats_config["listen_subjects_callbacks"] = subjects_and_callbacks
-        NATSClient.__init__(self, **self.nats_config)
-        asyncio.ensure_future(
-            self.client.publish(
-                f"panini_events.{self.service_name}.{self.client_id}.started",
-                b"{}",
-            )
-        )
-
-        self.tasks = self.tasks + self.TASKS
-        self.interval_tasks = self.INTERVAL_TASKS
-
-        self._start_tasks()
-
-    def _start_tasks(self):
         loop = asyncio.get_event_loop()
+
+        self._start_event()
         tasks = asyncio.all_tasks(loop)
-        for coro in self.tasks:
-            if not asyncio.iscoroutinefunction(coro):
-                raise InitializingTaskError("Only coroutine tasks allowed")
-            loop.create_task(coro())
-        for interval in self.interval_tasks:
-            for coro in self.interval_tasks[interval]:
-                if not asyncio.iscoroutinefunction(coro):
-                    raise InitializingIntervalTaskError(
-                        "Only coroutine interval tasks allowed"
-                    )
-                loop.create_task(coro())
+        self._task_manager.create_tasks()
+
         if self.http_server:
             self.http_server.start_server()
-        loop.run_until_complete(asyncio.gather(*tasks))
+        else:
+            loop.run_until_complete(asyncio.gather(*tasks))
+
+
 
 
 def get_app() -> App:
diff --git a/panini/http_server/http_server_app.py b/panini/http_server/http_server_app.py
index b809416..86e396a 100644
--- a/panini/http_server/http_server_app.py
+++ b/panini/http_server/http_server_app.py
@@ -1,18 +1,27 @@
+import asyncio
 from aiohttp import web
+from aiohttp.web_routedef import RouteTableDef
 
 
 class HTTPServer:
     def __init__(
         self,
-        base_app,
+        routes: RouteTableDef,
+        loop: asyncio.AbstractEventLoop,
         host: str = None,
         port: int = None,
         web_app: web.Application = None,
+        web_server_params=None,
     ):
-        self.app = base_app
-        self.routes = base_app.http
+        if web_server_params is None:
+            web_server_params = {}
+
+        self.routes = routes
         self.host = host
         self.port = port
+        self.web_server_params = web_server_params
+        self.loop = loop
+
         if web_app:
             self.web_app = web_app
         else:
@@ -23,4 +32,4 @@ def start_server(self):
 
     def _start_server(self):
         self.web_app.add_routes(self.routes)
-        web.run_app(self.web_app, host=self.host, port=self.port)
+        web.run_app(self.web_app, host=self.host, port=self.port, loop=self.loop, **self.web_server_params)
diff --git a/panini/managers.py b/panini/managers.py
deleted file mode 100644
index 46663f2..0000000
--- a/panini/managers.py
+++ /dev/null
@@ -1,323 +0,0 @@
-import time
-import asyncio
-from . import exceptions
-from typing import Callable, Type
-from .exceptions import (
-    ValidationError,
-    InitializingIntervalTaskError,
-    NotReadyError,
-    BaseError,
-)
-from .middleware import Middleware
-
-
-class _EventManager:
-    """
-    Collect all functions from each module wrapped by @app.subscription or @EventManager.subscribe
-    """
-
-    SUBSCRIPTIONS = {}
-
-    def listen(
-        self,
-        subject: list or str,
-        validator: type = None,
-        dynamic_subscription=False,
-        data_type="json.loads",
-    ):
-        def wrapper(function):
-            function = _EventManager.wrap_function_by_validator(function, validator)
-            if type(subject) is list:
-                for t in subject:
-                    _EventManager._check_subscription(t)
-                    _EventManager.SUBSCRIPTIONS[t].append(function)
-            else:
-                _EventManager._check_subscription(subject)
-                _EventManager.SUBSCRIPTIONS[subject].append(function)
-            function.data_type = data_type
-            return function
-
-        if dynamic_subscription:
-            if type(subject) is list:
-                for s in subject:
-                    self._subscribe(s, wrapper)
-            else:
-                self._subscribe(subject, wrapper)
-        return wrapper
-
-    def _subscribe(self, subject, function):
-        if not hasattr(self, "connector") or self.connector.check_connection is False:
-            raise NotReadyError(
-                "Something wrong. NATS client should be connected first"
-            )
-        self.subscribe_new_subject_sync(subject, function)
-
-    @staticmethod
-    def wrap_function_by_validator(function, validator):
-        def validate_message(msg):
-            try:
-                if validator is not None:
-                    validator.validated_message(msg.data)
-            except exceptions.ValidationError as se:
-                error = f"subject: {msg.subject} error: {str(se)}"
-                return {"success": False, "error": error}
-            except Exception as e:
-                raise ValidationError(e)
-            return msg
-
-        def wrapper(msg):
-            validate_message(msg)
-            return function(msg)
-
-        async def wrapper_async(msg):
-            validate_message(msg)
-            return await function(msg)
-
-        if asyncio.iscoroutinefunction(function):
-            return wrapper_async
-        else:
-            return wrapper
-
-    @staticmethod
-    def _check_subscription(subscription):
-        if subscription not in _EventManager.SUBSCRIPTIONS:
-            _EventManager.SUBSCRIPTIONS[subscription] = []
-
-
-class _TaskManager:
-    """
-    Collect all functions from each module wrapped by @app.task or @TaskManager.task
-    """
-
-    TASKS = []
-
-    @staticmethod
-    def task(**kwargs):
-        def wrapper(task):
-            _TaskManager._check_task(task)
-            _TaskManager.TASKS.append(task)
-            return task
-
-        return wrapper
-
-    @staticmethod
-    def _check_task(task):
-        # TODO
-        pass
-
-
-class _IntervalTaskManager:
-    """
-    Collect all functions from each module wrapped by @app.task or @TaskManager.task
-    """
-
-    INTERVAL_TASKS = {}
-
-    @staticmethod
-    def timer_task(interval: float or int):
-        def wrapper(interval_task: Callable):
-            _IntervalTaskManager._check_timer_task(interval, interval_task)
-            if asyncio.iscoroutinefunction(interval_task):
-                interval_task = _IntervalTaskManager.wrap_coro_by_interval(
-                    interval, interval_task
-                )
-            else:
-                interval_task = _IntervalTaskManager.wrap_function_by_interval(
-                    interval, interval_task
-                )
-            if interval not in _IntervalTaskManager.INTERVAL_TASKS:
-                _IntervalTaskManager.INTERVAL_TASKS[interval] = []
-            _IntervalTaskManager.INTERVAL_TASKS[interval].append(interval_task)
-            return interval_task
-
-        return wrapper
-
-    @staticmethod
-    def wrap_coro_by_interval(interval, interval_task):
-        async def wrapper(**kwargs):
-            while True:
-                try:
-                    await interval_task(**kwargs)
-                    await asyncio.sleep(interval)
-                except InitializingIntervalTaskError:
-                    # TODO: warning log
-                    pass
-
-        return wrapper
-
-    @staticmethod
-    def wrap_function_by_interval(interval, interval_task):
-        def wrapper(**kwargs):
-            while True:
-                try:
-                    interval_task(**kwargs)
-                    time.sleep(interval)
-                except InitializingIntervalTaskError:
-                    # TODO: warning log
-                    pass
-
-        return wrapper
-
-    @staticmethod
-    def _check_timer_task(interval, interval_task):
-        # TODO
-        pass
-
-
-class _MiddlewareManager:
-    MIDDLEWARE = {
-        "send_publish_middleware": [],
-        "listen_publish_middleware": [],
-        "send_request_middleware": [],
-        "listen_request_middleware": [],
-    }
-
-    @staticmethod
-    def add_middleware(middleware_cls: Type[Middleware], *args, **kwargs):
-        assert issubclass(
-            middleware_cls, Middleware
-        ), "Each custom middleware class must be a subclass of Middleware"
-        high_priority_functions = (
-            "send_publish",
-            "listen_publish",
-            "send_request",
-            "listen_request",
-        )
-        global_functions = ("send_any", "listen_any")
-
-        # check, that at least one function is implemented
-        assert any(
-            function in middleware_cls.__dict__
-            for function in high_priority_functions + global_functions
-        ), f"At least one of the following functions must be implemented: {high_priority_functions + global_functions}"
-
-        middleware_obj = middleware_cls(*args, **kwargs)
-        for function_name in high_priority_functions:
-            if function_name in middleware_cls.__dict__:
-                _MiddlewareManager.MIDDLEWARE[f"{function_name}_middleware"].append(
-                    getattr(middleware_obj, function_name)
-                )
-
-        if "send_any" in middleware_cls.__dict__:
-            if "send_publish" not in middleware_cls.__dict__:
-                _MiddlewareManager.MIDDLEWARE["send_publish_middleware"].append(
-                    middleware_obj.send_any
-                )
-
-            if "send_request" not in middleware_cls.__dict__:
-                _MiddlewareManager.MIDDLEWARE["send_request_middleware"].append(
-                    middleware_obj.send_any
-                )
-
-        if "listen_any" in middleware_cls.__dict__:
-            if "listen_publish" not in middleware_cls.__dict__:
-                _MiddlewareManager.MIDDLEWARE["listen_publish_middleware"].append(
-                    middleware_obj.listen_any
-                )
-
-            if "listen_request" not in middleware_cls.__dict__:
-                _MiddlewareManager.MIDDLEWARE["listen_request_middleware"].append(
-                    middleware_obj.listen_any
-                )
-
-    @staticmethod
-    def _wrap_function_by_middleware(function_type: str) -> Callable:
-        """
-        function: Callable - function to wrap
-        function_type: str - one of the ["listen", "publish", "request"]
-        """
-        assert function_type in (
-            "listen",
-            "publish",
-            "request",
-        ), "function type must be in (`listen`, `publish`, `request`)"
-
-        def decorator(function: Callable):
-            def wrap_function_by_send_middleware(
-                func: Callable, single_middleware
-            ) -> Callable:
-                def next_wrapper(subject: str, message, *args, **kwargs):
-                    return single_middleware(subject, message, func, *args, **kwargs)
-
-                async def async_next_wrapper(subject: str, message, *args, **kwargs):
-                    return await single_middleware(
-                        subject, message, func, *args, **kwargs
-                    )
-
-                if asyncio.iscoroutinefunction(single_middleware):
-                    return async_next_wrapper
-                else:
-                    return next_wrapper
-
-            def wrap_function_by_listen_middleware(
-                func: Callable, single_middleware
-            ) -> Callable:
-                def next_wrapper(msg):
-                    return single_middleware(msg, func)
-
-                async def async_next_wrapper(msg):
-                    return await single_middleware(msg, func)
-
-                if asyncio.iscoroutinefunction(single_middleware):
-                    return async_next_wrapper
-                else:
-                    return next_wrapper
-
-            def build_middleware_wrapper(
-                func: Callable, middleware_key: str, wrapper: Callable
-            ) -> Callable:
-                for middleware in _MiddlewareManager.MIDDLEWARE[middleware_key]:
-                    func = wrapper(func, middleware)
-                return func
-
-            if function_type == "publish":
-                return build_middleware_wrapper(
-                    function,
-                    "send_publish_middleware",
-                    wrap_function_by_send_middleware,
-                )
-
-            elif function_type == "request":
-                return build_middleware_wrapper(
-                    function,
-                    "send_request_middleware",
-                    wrap_function_by_send_middleware,
-                )
-
-            else:
-                if (
-                    len(_MiddlewareManager.MIDDLEWARE["listen_publish_middleware"]) == 0
-                    and len(_MiddlewareManager.MIDDLEWARE["listen_request_middleware"])
-                    == 0
-                ):
-                    return function
-
-                function_listen_publish = build_middleware_wrapper(
-                    function,
-                    "listen_publish_middleware",
-                    wrap_function_by_listen_middleware,
-                )
-                function_listen_request = build_middleware_wrapper(
-                    function,
-                    "listen_request_middleware",
-                    wrap_function_by_listen_middleware,
-                )
-
-                def listen_wrapper(msg) -> Callable:
-                    if msg.reply == "":
-                        return function_listen_publish(msg)
-                    else:
-                        return function_listen_request(msg)
-
-                async def async_listen_wrapper(msg) -> Callable:
-                    if msg.reply == "":
-                        return await function_listen_publish(msg)
-                    else:
-                        return await function_listen_request(msg)
-
-                if asyncio.iscoroutinefunction(function):
-                    return async_listen_wrapper
-                else:
-                    return listen_wrapper
-
-        return decorator
diff --git a/panini/managers/__init__.py b/panini/managers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/panini/managers/event_manager.py b/panini/managers/event_manager.py
new file mode 100644
index 0000000..23c389d
--- /dev/null
+++ b/panini/managers/event_manager.py
@@ -0,0 +1,65 @@
+import asyncio
+
+from panini import exceptions
+from panini.exceptions import NotReadyError, ValidationError
+
+
+class EventManager:
+    """
+    Collect all functions from each module wrapped by @app.subscription or @EventManager.subscribe
+    """
+
+    def __init__(self):
+        self._subscriptions = {}
+
+    @property
+    def subscriptions(self):
+        return self._subscriptions
+
+    def listen(
+        self,
+        subject: list or str,
+        validator: type = None,
+        data_type="json.loads",
+    ):
+        def wrapper(function):
+            function = self.wrap_function_by_validator(function, validator)
+            if type(subject) is list:
+                for t in subject:
+                    self._check_subscription(t)
+                    self._subscriptions[t].append(function)
+            else:
+                self._check_subscription(subject)
+                self._subscriptions[subject].append(function)
+            function.data_type = data_type
+            return function
+        return wrapper
+
+    def wrap_function_by_validator(self, function, validator):
+        def validate_message(msg):
+            try:
+                if validator is not None:
+                    validator.validated_message(msg.data)
+            except exceptions.ValidationError as se:
+                error = f"subject: {msg.subject} error: {str(se)}"
+                return {"success": False, "error": error}
+            except Exception as e:
+                raise ValidationError(e)
+            return msg
+
+        def wrapper(msg):
+            validate_message(msg)
+            return function(msg)
+
+        async def wrapper_async(msg):
+            validate_message(msg)
+            return await function(msg)
+
+        if asyncio.iscoroutinefunction(function):
+            return wrapper_async
+        else:
+            return wrapper
+
+    def _check_subscription(self, subscription):
+        if subscription not in self._subscriptions:
+            self._subscriptions[subscription] = []
diff --git a/panini/managers/middleware_manager.py b/panini/managers/middleware_manager.py
new file mode 100644
index 0000000..9677cd3
--- /dev/null
+++ b/panini/managers/middleware_manager.py
@@ -0,0 +1,175 @@
+import asyncio
+from typing import Callable, Type
+
+from panini.middleware import Middleware
+
+
+class MiddlewareManager:
+
+    def __init__(self):
+        self._middlewares = {
+            "send_publish_middleware": [],
+            "listen_publish_middleware": [],
+            "send_request_middleware": [],
+            "listen_request_middleware": [],
+        }
+
+    @property
+    def middlewares(self):
+        return self._middlewares
+
+    @middlewares.setter
+    def middlewares(self, value: dict):
+        self._middlewares = value
+
+    def add_middleware(self, middleware_cls: Type[Middleware], *args, **kwargs):
+        assert issubclass(middleware_cls, Middleware), \
+            "Each custom middleware class must be a subclass of Middleware"
+
+        high_priority_functions = (
+            "send_publish",
+            "listen_publish",
+            "send_request",
+            "listen_request",
+        )
+
+        global_functions = (
+            "send_any",
+            "listen_any"
+        )
+
+        # check, that at least one function is implemented
+        assert any(
+            function in middleware_cls.__dict__
+            for function in high_priority_functions + global_functions
+        ), f"At least one of the following functions must be implemented: {high_priority_functions + global_functions}"
+
+        middleware_obj = middleware_cls(*args, **kwargs)
+        for function_name in high_priority_functions:
+            if function_name in middleware_cls.__dict__:
+                self._middlewares[f"{function_name}_middleware"].append(
+                    getattr(middleware_obj, function_name)
+                )
+
+        if "send_any" in middleware_cls.__dict__:
+            if "send_publish" not in middleware_cls.__dict__:
+                self._middlewares["send_publish_middleware"].append(
+                    middleware_obj.send_any
+                )
+
+            if "send_request" not in middleware_cls.__dict__:
+                self._middlewares["send_request_middleware"].append(
+                    middleware_obj.send_any
+                )
+
+        if "listen_any" in middleware_cls.__dict__:
+            if "listen_publish" not in middleware_cls.__dict__:
+                self._middlewares["listen_publish_middleware"].append(
+                    middleware_obj.listen_any
+                )
+
+            if "listen_request" not in middleware_cls.__dict__:
+                self._middlewares["listen_request_middleware"].append(
+                    middleware_obj.listen_any
+                )
+
+    def wrap_function_by_middleware(self, function_type: str) -> Callable:
+        """
+        function: Callable - function to wrap
+        function_type: str - one of the ["listen", "publish", "request"]
+        """
+        assert function_type in (
+            "listen",
+            "publish",
+            "request",
+        ), "function type must be in (`listen`, `publish`, `request`)"
+
+        def decorator(function: Callable):
+            def wrap_function_by_send_middleware(
+                func: Callable, single_middleware
+            ) -> Callable:
+                def next_wrapper(subject: str, message, *args, **kwargs):
+                    return single_middleware(subject, message, func, *args, **kwargs)
+
+                async def async_next_wrapper(subject: str, message, *args, **kwargs):
+                    return await single_middleware(
+                        subject, message, func, *args, **kwargs
+                    )
+
+                if asyncio.iscoroutinefunction(single_middleware):
+                    return async_next_wrapper
+                else:
+                    return next_wrapper
+
+            def wrap_function_by_listen_middleware(
+                func: Callable, single_middleware
+            ) -> Callable:
+                def next_wrapper(msg):
+                    return single_middleware(msg, func)
+
+                async def async_next_wrapper(msg):
+                    return await single_middleware(msg, func)
+
+                if asyncio.iscoroutinefunction(single_middleware):
+                    return async_next_wrapper
+                else:
+                    return next_wrapper
+
+            def build_middleware_wrapper(
+                func: Callable, middleware_key: str, wrapper: Callable
+            ) -> Callable:
+                for middleware in self._middlewares[middleware_key]:
+                    func = wrapper(func, middleware)
+                return func
+
+            if function_type == "publish":
+                return build_middleware_wrapper(
+                    function,
+                    "send_publish_middleware",
+                    wrap_function_by_send_middleware,
+                )
+
+            elif function_type == "request":
+                return build_middleware_wrapper(
+                    function,
+                    "send_request_middleware",
+                    wrap_function_by_send_middleware,
+                )
+
+            else:
+                if (
+                    len(self._middlewares["listen_publish_middleware"]) == 0
+                    and len(self._middlewares["listen_request_middleware"])
+                    == 0
+                ):
+                    return function
+
+                function_listen_publish = build_middleware_wrapper(
+                    function,
+                    "listen_publish_middleware",
+                    wrap_function_by_listen_middleware,
+                )
+                function_listen_request = build_middleware_wrapper(
+                    function,
+                    "listen_request_middleware",
+                    wrap_function_by_listen_middleware,
+                )
+
+                def listen_wrapper(msg) -> Callable:
+                    if msg.reply == "":
+                        return function_listen_publish(msg)
+                    else:
+                        return function_listen_request(msg)
+
+                async def async_listen_wrapper(msg) -> Callable:
+                    if msg.reply == "":
+                        return await function_listen_publish(msg)
+                    else:
+                        return await function_listen_request(msg)
+
+                if asyncio.iscoroutinefunction(function):
+                    return async_listen_wrapper
+                else:
+                    return listen_wrapper
+
+        return decorator
diff --git a/panini/nats_client.py b/panini/managers/nats_client.py
similarity index 65%
rename from panini/nats_client.py
rename to panini/managers/nats_client.py
index 93e17bc..82bbbf7 100644
--- a/panini/nats_client.py
+++ b/panini/managers/nats_client.py
@@ -4,11 +4,13 @@
 import threading
 import nest_asyncio
 from types import CoroutineType
+from typing import Union, List
 from nats.aio.client import Client as NATS
 from panini.exceptions import DataTypeError
+from panini.managers.middleware_manager import MiddlewareManager
 from panini.utils.logger import get_logger
-from panini.managers import _MiddlewareManager
 
+NoneType = type(None)
 nest_asyncio.apply()
 
 
@@ -39,17 +41,19 @@ def __repr__(self):
 
 class NATSClient:
     def __init__(
-        self,
-        client_id: str,
-        host: str,
-        port: int or str,
-        listen_subjects_callbacks: dict,
-        allow_reconnect: bool or None,
-        max_reconnect_attempts: int = 60,
-        reconnecting_time_wait: int = 2,
-        auth: dict = None,
-        queue="",
-        pending_bytes_limit=65536 * 1024 * 10,
+            self,
+            host: Union[str, NoneType],
+            port: Union[int, str],
+            servers: Union[List[str], NoneType],
+            client_id: str,
+            loop: asyncio.AbstractEventLoop,
+            allow_reconnect: Union[bool, NoneType],
+            max_reconnect_attempts: int = 60,
+            reconnecting_time_wait: int = 2,
+            auth: Union[dict, NoneType] = None,
+            queue="",
+            pending_bytes_limit=65536 * 1024 * 10,
+            **kwargs
     ):
         """
         :param client_id: instance identifier for NATS, str
@@ -65,32 +69,67 @@ def __init__(
         self.client_id = client_id
         self.host = host
         self.port = port
+        self.servers = servers
         self.queue = queue
         self.auth = auth
-        self.listen_subjects_callbacks = listen_subjects_callbacks
+        self.listen_subjects_callbacks = None
         self.allow_reconnect = allow_reconnect
         self.max_reconnect_attempts = max_reconnect_attempts
         self.reconnecting_time_wait = reconnecting_time_wait
         self.pending_bytes_limit = pending_bytes_limit
         self.ssid_map = {}
 
+        self.include_subjects = None
+        self.exclude_subjects = None
+
+        self._middleware_manager = MiddlewareManager()
+
+        def not_assigned_method(*args, **kwargs):
+            raise Exception("used before assignment")
+
+        self._publish_wrapped = not_assigned_method
+        self._request_wrapped = not_assigned_method
+
+        self._connection_kwargs = kwargs
+
+        self.loop = loop
+
+    def set_listen_subjects_callbacks(self, subscriptions: list):
+        subscriptions = self.filter_subjects(subscriptions)
+        self.listen_subjects_callbacks = subscriptions
+
+    def start(self):
         # inject send_middlewares
-        self._publish_wrapped = _MiddlewareManager._wrap_function_by_middleware(
+        self._publish_wrapped = self._middleware_manager.wrap_function_by_middleware(
             "publish"
         )(self._publish)
-        self._request_wrapped = _MiddlewareManager._wrap_function_by_middleware(
+        self._request_wrapped = self._middleware_manager.wrap_function_by_middleware(
             "request"
         )(self._request)
 
-        self.loop = asyncio.get_event_loop()
         self.loop.run_until_complete(self._establish_connection())
 
+    @property
+    def middleware_manager(self):
+        return self._middleware_manager
+
+    @property
+    def middlewares(self):
+        return self._middleware_manager.middlewares
+
+    @middlewares.setter
+    def middlewares(self, value: dict):
+        self._middleware_manager.middlewares = value
+
     async def _establish_connection(self):
-        # TODO: authorization
         self.client = NATS()
         self.client.msg_class = Msg
-        self.server = self.host + ":" + str(self.port)
-        kwargs = {"servers": self.server, "loop": self.loop, "name": self.client_id}
+
+        if self.servers is None:
+            server = 'nats://' + self.host + ":" + str(self.port)
+            self.servers = [server]
+
+        kwargs = {"servers": self.servers, "loop": self.loop, "name": self.client_id, **self._connection_kwargs}
         if self.allow_reconnect:
             kwargs["allow_reconnect"] = self.allow_reconnect
         if self.max_reconnect_attempts:
@@ -104,23 +143,35 @@ async def _establish_connection(self):
             for subject, callbacks in listen_subjects_callbacks.items():
                 for callback in callbacks:
                     await self.subscribe_new_subject(
-                            subject, callback, init_subscription=True
-                        )
+                        subject, callback, init_subscription=True
+                    )
+            self.print_connect()
+
+    def print_connect(self):
+        print('\n======================================================================================')
+        print(f'Panini service connected to NATS..')
+        print(f"id: {self.client.client_id}")
+        print(f"name: {self.client_id}")
+        print(f'\nNATS brokers:')
+        for i in self.servers:
+            print("* ",i)
+        print('======================================================================================\n')
 
     def subscribe_new_subject_sync(self, subject: str, callback: CoroutineType, **kwargs):
         self.loop.run_until_complete(self.subscribe_new_subject(subject, callback, **kwargs))
 
     async def subscribe_new_subject(
-        self,
-        subject: str,
-        callback: CoroutineType,
-        init_subscription=False,
-        is_async=False,
-        data_type=None
+            self,
+            subject: str,
+            callback: CoroutineType,
+            init_subscription=False,
+            is_async=False,
+            data_type=None
     ):
         if data_type == None:
             data_type = getattr(callback, 'data_type', 'json.loads')
-        callback = _MiddlewareManager._wrap_function_by_middleware("listen")(callback)
+
+        callback = self._middleware_manager.wrap_function_by_middleware("listen")(callback)
         wrapped_callback = _ReceivedMessageHandler(self._publish, callback, data_type)
         ssid = await self.client.subscribe(
             subject,
@@ -168,12 +219,12 @@ async def unsubscribe_ssid(self, ssid: int, subject: str = None):
                     self.listen_subjects_callbacks[subject].remove(ssid)
 
     def publish_sync(
-        self,
-        subject: str,
-        message,
-        reply_to: str = None,
-        force: bool = False,
-        data_type: type or str = "json.dumps",
+            self,
+            subject: str,
+            message,
+            reply_to: str = None,
+            force: bool = False,
+            data_type: type or str = "json.dumps",
     ):
         asyncio.ensure_future(
             self.publish(subject, message, reply_to, force, data_type)
@@ -183,12 +234,12 @@ def publish_from_another_thread(self, subject: str, message):
         self.loop.call_soon_threadsafe(self.publish_sync, subject, message)
 
     def request_sync(
-        self,
-        subject: str,
-        message,
-        timeout: int = 10,
-        data_type: type or str = "json.dumps",
-        callback: typing.Callable = None,
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
+            data_type: type or str = "json.dumps",
+            callback: typing.Callable = None,
     ):
         # asyncio.ensure_future(self.request(subject, message, timeout, data_type))
         return self.loop.run_until_complete(
@@ -196,10 +247,10 @@ def request_sync(
         )
 
     def request_from_another_thread_sync(
-        self,
-        subject: str,
-        message,
-        timeout: int = 10,
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
     ):
         try:
             loop = asyncio.get_event_loop()
@@ -210,10 +261,10 @@ def request_from_another_thread_sync(
         )
 
     async def request_from_another_thread(
-        self,
-        subject: str,
-        message,
-        timeout: int = 10,
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
     ):
         # return self.loop.call_soon_threadsafe(self.request_sync, subject, message, timeout)
         fut = asyncio.run_coroutine_threadsafe(
@@ -245,12 +296,12 @@ def format_message_data_type(message, data_type):
         return message
 
     async def _publish(
-        self,
-        subject: str,
-        message,
-        reply_to: str = None,
-        force: bool = False,
-        data_type: type or str = "json.dumps",
+            self,
+            subject: str,
+            message,
+            reply_to: str = None,
+            force: bool = False,
+            data_type: type or str = "json.dumps",
     ):
         message = self.format_message_data_type(message, data_type)
 
@@ -262,12 +313,12 @@ async def _publish(
             await self.client.flush()
 
     async def publish(
-        self,
-        subject: str,
-        message,
-        reply_to: str = None,
-        force: bool = False,
-        data_type: type or str = "json.dumps",
+            self,
+            subject: str,
+            message,
+            reply_to: str = None,
+            force: bool = False,
+            data_type: type or str = "json.dumps",
     ):
         return await self._publish_wrapped(
             subject=subject,
@@ -278,12 +329,12 @@ async def publish(
         )
 
     async def _request(
-        self,
-        subject: str,
-        message,
-        timeout: int = 10,
-        data_type: type or str = "json.dumps",
-        callback: typing.Callable = None,
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
+            data_type: type or str = "json.dumps",
+            callback: typing.Callable = None,
     ):
         message = self.format_message_data_type(message, data_type)
         if callback is not None:
@@ -297,12 +348,12 @@ async def _request(
         return response
 
     async def request(
-        self,
-        subject: str,
-        message,
-        timeout: int = 10,
-        data_type: type or str = "json.dumps",
-        callback: typing.Callable = None,
+            self,
+            subject: str,
+            message,
+            timeout: int = 10,
+            data_type: type or str = "json.dumps",
+            callback: typing.Callable = None,
     ):
         return await self._request_wrapped(
             subject=subject,
@@ -325,6 +376,32 @@ def check_connection(self):
             return True
         self.log.warning("NATS Client status: DISCONNECTED")
 
+    def add_filters(self, include: list = None, exclude: list = None):
+        self.include_subjects = include
+        self.exclude_subjects = exclude
+
+    def filter_subjects(self, subscriptions):
+        assert not self.include_subjects or not self.exclude_subjects, "You can use either include or exclude. Not both"
+
+        if self.include_subjects:
+            for subject in subscriptions.copy():
+                success = False
+                for subject_include in self.include_subjects:
+                    if subject_include in subject:
+                        success = True
+                        break
+                if not success:
+                    del subscriptions[subject]
+
+        if self.exclude_subjects:
+            for subject in subscriptions.copy():
+                for subject_exclude in self.exclude_subjects:
+                    if subject_exclude in subject:
+                        del subscriptions[subject]
+                        break
+
+        return subscriptions
+
 
 class _ReceivedMessageHandler:
     def __init__(self, publish_func, cb, data_type):
diff --git a/panini/managers/task_manager.py b/panini/managers/task_manager.py
new file mode 100644
index 0000000..c1db8be
--- /dev/null
+++ b/panini/managers/task_manager.py
@@ -0,0 +1,80 @@
+import asyncio
+import time
+from typing import Union
+
+from panini.exceptions import InitializingTaskError
+
+
+class TaskManager:
+    """
+    Collect all functions from each module wrapped by @app.task or @TaskManager.task
+    """
+
+    def __init__(self):
+        self._tasks = []
+        self._loop = asyncio.get_event_loop()
+
+    @property
+    def tasks(self):
+        return self._tasks
+
+    # def __call__(self, interval: float or int = None, **kwargs):
+    #     def wrapper(task):
+    #         if interval:
+    #             self.register_interval_task(interval, task)
+    #         else:
+    #             self.register_single_task(task)
+    #         self.register_single_task(task)
+    #         return task
+    #
+    #     return wrapper
+
+    def register_task(self, interval: Union[float, int] = None):
+        def wrapper(task):
+            self._check_task(task)
+            if interval:
+                task = self.wrapper_for_interval_task(interval, task)
+            self._tasks.append(task)
+
+        return wrapper
+
+    def register_single_task(self):
+        def wrapper(task):
+            self._check_task(task)
+            self._tasks.append(task)
+        # self._loop.create_task(task())
+        return wrapper
+
+    def register_interval_task(self, interval: int or float):
+        def wrapper(task):
+            self._check_task(task)
+            interval_task = self.wrapper_for_interval_task(interval, task)
+            self._tasks.append(interval_task)
+            # self._loop.create_task(interval_task())
+        return wrapper
+
+    def wrapper_for_interval_task(self, interval, task):
+        async def wrapper(**kwargs):
+            while True:
+                try:
+                    start = time.time()
+                    await task(**kwargs)
+                    duration = time.time() - start
+
+                    if duration < interval:
+                        await asyncio.sleep(interval - duration)
+
+                except InitializingTaskError:
+                    # TODO: warning log
+                    pass
+
+        return wrapper
+
+    def _check_task(self, task):
+        if not asyncio.iscoroutinefunction(task):
+            raise InitializingTaskError("Only coroutine tasks allowed")
+
+    def create_tasks(self):
+        loop = asyncio.get_event_loop()
+        for task in self._tasks:
+            loop.create_task(task())
diff --git a/panini/middleware/reader_emulator_middleware.py b/panini/middleware/reader_emulator_middleware.py
index 566c345..66ff7c5 100644
--- a/panini/middleware/reader_emulator_middleware.py
+++ b/panini/middleware/reader_emulator_middleware.py
@@ -5,7 +5,7 @@
 
 from panini.emulator_client import EmulatorClient
 from panini.middleware import Middleware
-from panini.nats_client import Msg
+from panini.managers.nats_client import Msg
 from panini.utils.helper import start_thread
 
 
diff --git a/panini/utils/logger.py b/panini/utils/logger.py
index 9032316..34e7a50 100644
--- a/panini/utils/logger.py
+++ b/panini/utils/logger.py
@@ -66,15 +66,13 @@ def set_logger(
         (
             logger_queue,
             log_stop_event,
-            log_process,
-            change_log_config_listener_queue,
+            log_process
         ) = _set_log_recorder_process(logger_config)
         _set_main_logging_config(logger_queue)
         return (
             logger_queue,
             log_stop_event,
-            log_process,
-            change_log_config_listener_queue,
+            log_process
         )
     else:
         logging.config.dictConfig(logger_config)
@@ -254,25 +252,18 @@ def _emergency_logging() -> None:
 def _dedicated_listener_process(
     log_listener_queue: Queue,
     stop_event: Event,
-    config: dict,
-    change_config_listener_queue: Queue,
+    config: dict
 ) -> None:
     logging.config.dictConfig(config)
     log_listener = logging.handlers.QueueListener(log_listener_queue, LogHandler())
-    change_log_config_listener = logging.handlers.QueueListener(
-        change_config_listener_queue, ChangeConfigHandler()
-    )
-    change_log_config_listener.start()
     log_listener.start()
     stop_event.wait()
     log_listener.stop()
-    change_log_config_listener.stop()
 
 
-def _set_log_recorder_process(config: dict) -> (Queue, Event, Process, Queue):
+def _set_log_recorder_process(config: dict) -> (Queue, Event, Process):
     try:
         log_listener_queue = Queue()
-        change_log_config_listener_queue = Queue()
         stop_event = Event()
         listener_process = Process(
             target=_dedicated_listener_process,
@@ -280,16 +271,14 @@ def _set_log_recorder_process(config: dict) -> (Queue, Event, Process, Queue):
             args=(
                 log_listener_queue,
                 stop_event,
-                config,
-                change_log_config_listener_queue,
+                config
             ),
         )
         listener_process.start()
         return (
             log_listener_queue,
             stop_event,
-            listener_process,
-            change_log_config_listener_queue,
+            listener_process
         )
 
     except Exception as e:
diff --git a/requirements/defaults.txt b/requirements/defaults.txt
index 2b935ad..5ea22ff 100644
--- a/requirements/defaults.txt
+++ b/requirements/defaults.txt
@@ -1,7 +1,7 @@
-aiohttp==3.7.4
+aiohttp==3.8.0
 aiohttp-cors==0.7.0
-async-timeout==3.0.1
-asyncio-nats-client==0.11.2
+async-timeout==4.0.0
+asyncio-nats-client==0.11.4
 requests==2.24.0
 six==1.15.0
 nats-python==0.8.0
diff --git a/setup.py b/setup.py
index 8d5421d..32d536e 100644
--- a/setup.py
+++ b/setup.py
@@ -19,14 +19,14 @@
 
 setup(
     name="panini",
-    version="0.5.0",
+    version="0.6.2",
     description="A python messaging framework for microservices based on NATS",
     long_description=long_description,
     long_description_content_type="text/x-rst",
     url="https://github.com/lwinterface/panini",
-    author="Op Return SA, developers: Andrii Volotskov, Danylo Tiutiushkin, Oleksii Volotskov",
+    author="Op Return SA, developers: Andrii Volotskov, Danylo Tiutiushkin",
     author_email="example@example.com",
-    python_requires=">=3.8.2",
+    python_requires=">=3.8.3",
     license="MIT",
     classifiers=[
         "Development Status :: 3 - Alpha",
@@ -45,12 +45,13 @@
         "panini.utils",
         "panini.http_server",
         "panini.middleware",
+        "panini.managers",
     ],
     install_requires=[
-        "aiohttp>=3.7.4",
+        "aiohttp>=3.8.0",
         "aiohttp-cors>=0.7.0",
-        "async-timeout>=3.0.1",
-        "asyncio-nats-client>=0.11.2",
+        "async-timeout==4.0.0",
+        "asyncio-nats-client>=0.11.4",
         "websocket-client>=0.58.0",
         "requests>=2.24.0",
         "six>=1.15.0",


Test patch:
diff --git a/panini/async_test_client.py b/panini/async_test_client.py
index f22ae81..510d7b0 100644
--- a/panini/async_test_client.py
+++ b/panini/async_test_client.py
@@ -13,7 +13,7 @@
 
 from .exceptions import TestClientError
 from .utils.helper import start_process
-from panini.nats_client import Msg
+from panini.managers.nats_client import Msg
 
 
 # Annotations for `Session.request()`
diff --git a/panini/test_client.py b/panini/test_client.py
index d8bbf24..ec169cf 100644
--- a/panini/test_client.py
+++ b/panini/test_client.py
@@ -3,6 +3,7 @@
 import threading
 import json
 import random
+import time
 import typing
 from urllib.parse import urljoin
 
@@ -12,7 +13,7 @@
 
 from .exceptions import TestClientError
 from .utils.helper import start_process, start_thread
-from panini.nats_client import Msg
+from panini.managers.nats_client import Msg
 
 
 # Annotations for `Session.request()`
diff --git a/tests/test_middleware.py b/tests/test_middleware.py
index 6f5415b..d32401c 100644
--- a/tests/test_middleware.py
+++ b/tests/test_middleware.py
@@ -88,7 +88,7 @@ async def request(msg):
 
 @pytest.fixture(scope="module")
 def client():
-    client = TestClient(run_panini, socket_timeout=200)
+    client = TestClient(run_panini, socket_timeout=200, listener_socket_timeout=200)
 
     @client.listen("test_middleware.publish.response")
     def publish_listener(msg):
diff --git a/tests/test_middleware_manager.py b/tests/test_middleware_manager.py
index ce8cfc8..c57dc99 100644
--- a/tests/test_middleware_manager.py
+++ b/tests/test_middleware_manager.py
@@ -3,7 +3,7 @@
 import pytest
 
 from panini import app as panini_app
-from panini.managers import _MiddlewareManager
+
 from panini.middleware import Middleware
 
 
@@ -92,14 +92,14 @@ def app():
         host="127.0.0.1",
         port=4222,
     )
-    _MiddlewareManager.MIDDLEWARE = {
+    app.nats.middlewares = {
         "send_publish_middleware": [],
         "listen_publish_middleware": [],
         "send_request_middleware": [],
         "listen_request_middleware": [],
     }
     yield app
-    _MiddlewareManager.MIDDLEWARE = {
+    app.nats.middlewares = {
         "send_publish_middleware": [],
         "listen_publish_middleware": [],
         "send_request_middleware": [],
@@ -109,7 +109,7 @@ def app():
 
 def test_foo_middleware(app):
     app.add_middleware(FooMiddleware)
-    middleware_dict = _MiddlewareManager.MIDDLEWARE
+    middleware_dict = app.nats.middlewares
     assert len(middleware_dict["send_publish_middleware"]) == 1
     assert len(middleware_dict["send_request_middleware"]) == 0
     assert len(middleware_dict["listen_publish_middleware"]) == 1
@@ -131,7 +131,7 @@ def test_foo_middleware(app):
 
 def test_bar_middleware(app):
     app.add_middleware(BarMiddleware)
-    middleware_dict = _MiddlewareManager.MIDDLEWARE
+    middleware_dict = app.nats.middlewares
 
     assert len(middleware_dict["send_publish_middleware"]) == 1
     assert len(middleware_dict["send_request_middleware"]) == 1
@@ -158,7 +158,7 @@ def test_bar_middleware(app):
 
 def test_foo_bar_middleware(app):
     app.add_middleware(FooBarMiddleware, "foo", bar="bar")
-    middleware_dict = _MiddlewareManager.MIDDLEWARE
+    middleware_dict = app.nats.middlewares
     assert len(middleware_dict["send_publish_middleware"]) == 1
     assert len(middleware_dict["send_request_middleware"]) == 1
     assert len(middleware_dict["listen_publish_middleware"]) == 0
@@ -183,7 +183,7 @@ def test_multiple_middlewares_order(app):
     app.add_middleware(FooMiddleware)
     app.add_middleware(BarMiddleware)
     app.add_middleware(FooBarMiddleware, "foo", bar="bar")
-    middleware_dict = _MiddlewareManager.MIDDLEWARE
+    middleware_dict = app.nats.middlewares
     assert len(middleware_dict["send_publish_middleware"]) == 3
     assert len(middleware_dict["send_request_middleware"]) == 2
     assert len(middleware_dict["listen_publish_middleware"]) == 2
@@ -241,7 +241,7 @@ def foo(subject, message, **kwargs):
 
     app.add_middleware(IncSyncMiddleware)
     for send in ("publish", "request"):
-        wrapped_function = app._wrap_function_by_middleware(send)(foo)
+        wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware(send)(foo)
         temp_message = {"data": 1}
         wrapped_function("", temp_message)
         assert temp_message["data"] == 7
@@ -257,7 +257,7 @@ def bar(msg):
         msg.data -= 2
 
     app.add_middleware(DecSyncMiddleware)
-    wrapped_function = app._wrap_function_by_middleware("listen")(bar)
+    wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware("listen")(bar)
     temp_message = Bar()
     wrapped_function(temp_message)
     assert temp_message.data == -1
@@ -272,7 +272,7 @@ async def foo(subject, message, **kwargs):
     loop = asyncio.get_event_loop()
 
     for send in ("publish", "request"):
-        wrapped_function = app._wrap_function_by_middleware(send)(foo)
+        wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware(send)(foo)
         temp_message = {"data": 1}
         loop.run_until_complete(wrapped_function("", temp_message))
         assert temp_message["data"] == 7
@@ -290,7 +290,7 @@ async def bar(msg):
     loop = asyncio.get_event_loop()
 
     app.add_middleware(DecMiddleware)
-    wrapped_function = app._wrap_function_by_middleware("listen")(bar)
+    wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware("listen")(bar)
     temp_message = Bar()
     loop.run_until_complete(wrapped_function(temp_message))
     assert temp_message.data == -1
@@ -315,12 +315,12 @@ def bar(msg):
     app.add_middleware(DecSyncMiddleware)
 
     for send in ("publish", "request"):
-        wrapped_function = app._wrap_function_by_middleware(send)(foo)
+        wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware(send)(foo)
         temp_message = {"data": 1}
         wrapped_function("", temp_message)
         assert temp_message["data"] == 11
 
-    wrapped_function = app._wrap_function_by_middleware("listen")(bar)
+    wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware("listen")(bar)
     temp_message = Bar()
     wrapped_function(temp_message)
     assert temp_message.data == -5
@@ -347,12 +347,12 @@ async def bar(msg):
     loop = asyncio.get_event_loop()
 
     for send in ("publish", "request"):
-        wrapped_function = app._wrap_function_by_middleware(send)(foo)
+        wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware(send)(foo)
         temp_message = {"data": 1}
         loop.run_until_complete(wrapped_function("", temp_message))
         assert temp_message["data"] == 11
 
-    wrapped_function = app._wrap_function_by_middleware("listen")(bar)
+    wrapped_function = app.nats.middleware_manager.wrap_function_by_middleware("listen")(bar)
     temp_message = Bar()
     loop.run_until_complete(wrapped_function(temp_message))
     assert temp_message.data == -5
diff --git a/tests/test_parameter_listen_subject_only_if_exclude.py b/tests/test_parameter_listen_subject_only_if_exclude.py
index 742fa17..f5c060a 100644
--- a/tests/test_parameter_listen_subject_only_if_exclude.py
+++ b/tests/test_parameter_listen_subject_only_if_exclude.py
@@ -9,10 +9,11 @@ def run_panini():
         service_name="test_parameter_listen_subject_only_if_exclude",
         host="127.0.0.1",
         port=4222,
-        listen_subject_only_if_exclude=["start"],
         logger_in_separate_process=False,
     )
 
+
+
     @app.listen("test_parameter_listen_subject_only_if_exclude.start")
     async def start(msg):
         return {"data": 1}
@@ -25,6 +26,7 @@ async def foo(msg):
     async def start(msg):
         return {"data": 3}
 
+    app.add_filters(exclude=["start"])
     app.start()
 
 
diff --git a/tests/test_parameter_listen_subject_only_if_include.py b/tests/test_parameter_listen_subject_only_if_include.py
index fdc6e7e..acbf5a1 100755
--- a/tests/test_parameter_listen_subject_only_if_include.py
+++ b/tests/test_parameter_listen_subject_only_if_include.py
@@ -9,9 +9,9 @@ def run_panini():
         service_name="test_listen_subject_only_if_include",
         host="127.0.0.1",
         port=4222,
-        listen_subject_only_if_include=["foo", "bar"],
         logger_in_separate_process=False,
     )
+    app.add_filters(include=["foo", "bar"])
 
     @app.listen("test_parameter_listen_subject_only_if_include.start")
     async def start(msg):
diff --git a/tests/test_timer_task.py b/tests/test_timer_task.py
index a05e367..572eb46 100755
--- a/tests/test_timer_task.py
+++ b/tests/test_timer_task.py
@@ -42,6 +42,7 @@ def foo_listener(msg):
 def test_timer_task(client):
     assert global_object.another_variable == 0
     client.wait(20)
+    # client.wait(20 * 100000)
     start_time = time.time()
     assert global_object.another_variable == 40
     client.wait(20)
diff --git a/tests/test_timer_task_new_interface.py b/tests/test_timer_task_new_interface.py
new file mode 100755
index 0000000..7367d30
--- /dev/null
+++ b/tests/test_timer_task_new_interface.py
@@ -0,0 +1,50 @@
+import time
+
+import pytest
+
+from panini.test_client import TestClient
+from panini import app as panini_app
+
+from tests.helper import Global
+
+
+def run_panini():
+    app = panini_app.App(
+        service_name="test_timer_task",
+        host="127.0.0.1",
+        port=4222,
+        logger_in_separate_process=False,
+    )
+
+    @app.task(0.1)
+    async def publish_periodically():
+        await app.publish(subject="test_timer_task.foo", message={})
+
+    app.start()
+
+
+global_object = Global()
+
+
+@pytest.fixture(scope="module")
+def client():
+    client = TestClient(run_panini)
+
+    @client.listen("test_timer_task.foo")
+    def foo_listener(msg):
+        global_object.another_variable += 2
+
+    client.start(do_always_listen=False)
+    yield client
+    client.stop()
+
+
+def test_timer_task(client):
+    assert global_object.another_variable == 0
+    client.wait(20)
+    # client.wait(20 * 100000)
+    start_time = time.time()
+    assert global_object.another_variable == 40
+    client.wait(20)
+    assert global_object.another_variable == 80
+    assert time.time() - start_time >= 0.4
diff --git a/tests/test_web_server.py b/tests/test_web_server.py
index 34e9493..141e64f 100755
--- a/tests/test_web_server.py
+++ b/tests/test_web_server.py
@@ -12,11 +12,11 @@ def run_panini():
         service_name="test_web_server",
         host="127.0.0.1",
         port=4222,
-        web_server=True,
-        web_port=8084,
-        logger_in_separate_process=False,
+        logger_in_separate_process=False
     )
 
+    app.setup_web_server(port=8084)
+
     @app.listen("test_web_server.foo")
     async def foo(msg):
         msg.data["data"] += 1
diff --git a/tests/test_web_server_separately.py b/tests/test_web_server_separately.py
index e0b7483..1de0a2f 100755
--- a/tests/test_web_server_separately.py
+++ b/tests/test_web_server_separately.py
@@ -13,11 +13,11 @@ def run_panini():
         service_name="test_web_server_separately",
         host="127.0.0.1",
         port=4222,
-        web_server=True,
-        web_port=8083,
-        logger_in_separate_process=False,
+        logger_in_separate_process=False
     )
 
+    app.setup_web_server(port=8083)
+
     @app.http.get("/test_web_server_separately/get")
     async def get_listener(request):
         return web.Response(text="get response")
diff --git a/tests/test_wss.py b/tests/test_wss.py
index 92080bf..c980a9d 100644
--- a/tests/test_wss.py
+++ b/tests/test_wss.py
@@ -14,11 +14,10 @@ def run_panini():
         service_name="test_wss",
         host="127.0.0.1",
         port=4222,
-        web_server=True,
-        web_port=1111,
-        logger_in_separate_process=False,
+        logger_in_separate_process=False
     )
 
+    app.setup_web_server(port=1111)
     manager = WSSManager(app)
 
     @app.listen("test_wss.start")
@@ -31,7 +30,7 @@ async def web_endpoint_listener(request):
         await ws.prepare(request)
         connection_id = str(uuid.uuid4())[:10]
         await ws.send_str(
-            json.dumps({"success": True, "data": "Successfully connected"})
+            json.dumps({"success": True, "message": "Successfully connected"})
         )
         await manager.client_listener(ws, connection_id)
         await ws.close()
@@ -68,10 +67,11 @@ def test_wss_bridge(client):
     client.websocket_session.send(json.dumps(subscribe_message))
     response = json.loads(client.websocket_session.recv())
     assert response["success"] is True
-    assert response["data"] == "Successfully connected"
+    assert response["message"] == "Successfully connected"
 
     response = json.loads(client.websocket_session.recv())
-    assert response["data"] == "Successfully connected to events: 'test_wss.foo.bar'"
+    print(response)
+    assert response["message"] == "Successfully connected to events: 'test_wss.foo.bar'"
 
     client.publish("test_wss.start", {})
     response = json.loads(client.websocket_session.recv())["data"]
@@ -85,6 +85,6 @@ def test_wss_bridge(client):
     response = json.loads(client.websocket_session.recv())
 
     assert response["success"] is True
-    assert response["data"] == "Successfully unsubscribed from event: test_wss.foo.bar"
+    assert response["message"] == "Successfully unsubscribed from event: test_wss.foo.bar"
 
     client.websocket_session.close()