{"id":26929283,"url":"https://github.com/jonathansilva/nano","last_synced_at":"2026-03-05T17:21:00.874Z","repository":{"id":284274387,"uuid":"922371175","full_name":"jonathansilva/nano","owner":"jonathansilva","description":"Nano framework PHP para desenvolvimento de API's e aplicações Web","archived":false,"fork":false,"pushed_at":"2025-03-25T03:05:44.000Z","size":17,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-07-09T03:47:59.217Z","etag":null,"topics":["framework","framework-php","rest-api","vanilla-php","web-development"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonathansilva.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-26T02:33:08.000Z","updated_at":"2025-03-25T03:05:48.000Z","dependencies_parsed_at":"2025-03-25T04:20:14.248Z","dependency_job_id":null,"html_url":"https://github.com/jonathansilva/nano","commit_stats":null,"previous_names":["jonathansilva/nano"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/jonathansilva/nano","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathansilva%2Fnano","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathansilva%2Fnano/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathansilva%2Fnano/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathansilva%2Fnano/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonathansilva","download_url":"https://codeload.github.com/jonathansilva/nano/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathansilva%2Fnano/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265573519,"owners_count":23790455,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","framework-php","rest-api","vanilla-php","web-development"],"created_at":"2025-04-02T05:19:28.441Z","updated_at":"2026-03-05T17:21:00.837Z","avatar_url":"https://github.com/jonathansilva.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nano\n\n*Nano framework PHP* para desenvolvimento de API's e aplicações Web\n\n**Requisitos**\n\n* Composer\n* PHP \u003e= 8.4.0\n* MySQL\n\nCertifique-se de que as extensões abaixo, estejam habilitadas no *php.ini*\n\n* extension=pdo_mysql\n* extension=mbstring\n* extension=curl\n\n**Instalação**\n\n`composer require jonathansilva/nano`\n\n**Configuração**\n\n*Apache*\n\n```apache\nRewriteEngine On\nOptions All -Indexes\n\nRewriteCond %{SCRIPT_FILENAME} !-f\nRewriteCond %{SCRIPT_FILENAME} !-d\nRewriteRule ^(.*)$ index.php?uri=/$1 [L,QSA]\n```\n\n*Nginx*\n\n```nginx\nlocation / {\n    if ($script_filename !~ \"-f\") {\n        rewrite ^(.*)$ /index.php?uri=/$1 break;\n    }\n}\n```\n\n.env.example\n\n```\nDB_HOST=localhost\nDB_USER=root\nDB_PASS=password\nDB_NAME=database\n\nJWT_KEY=anything\nJWT_EXP_IN_HOURS=8\n\nCOOKIE_EXP_IN_DAYS=1\nCOOKIE_DOMAIN=localhost\nCOOKIE_HTTPS=false\nCOOKIE_HTTPONLY=true\nCOOKIE_SAMESITE=Strict\n\nCURL_SSL_VERIFYPEER=false\n\nTEMPLATE_ENGINE_CACHE=false\n```\n\nDuplique o arquivo, renomeie para **.env** e altere os valores\n\n.gitignore\n\n```\n.idea\n.env\n.vscode/\nvendor/\ncache/\ncomposer.phar\ncomposer.lock\n```\n\nindex.php\n\n```php\n\u003c?php\n\nrequire_once __DIR__ . '/vendor/autoload.php';\n\n$app = Nano\\Core\\Router\\Instance::create();\n\n$app-\u003euse('App\\Middleware\\Token\\Assert');\n\n$app-\u003enotFound('App\\Callback\\Page\\NotFound');\n\n$app-\u003eget('/about', fn ($req, $res) =\u003e $res-\u003eview('about'));\n\n$app-\u003eget('/hello/{name}', function ($req, $res) {\n    echo $req-\u003eparams()-\u003ename;\n});\n\n$app-\u003epost('/api/test', function ($req, $res) {\n    $res-\u003ejson(200, array('message' =\u003e 'Hello World!'));\n});\n\n$app-\u003estart();\n```\n\n# Routes\n\nVerbos: GET, POST, PUT, PATCH e DELETE\n\n```php\n$app-\u003eget(\n    '/me', // Path\n    'App\\Callback\\Page\\Me', // Callback\n    ['App\\Middleware\\Token\\Ensure'] // Middleware\n);\n```\n\nO Callback/Controller não permite chamada de método\n\n❌\n\n```php\n$app-\u003eget('/login', 'App\\Callback\\Page\\Login@index');\n```\n\n✔️\n\n```php\n$app-\u003eget('/login', 'App\\Callback\\Page\\Login');\n```\n\nCrie o método 'handle'\n\n```php\nclass Login\n{\n    public function handle($req, $res)\n    {\n        // TODO\n    }\n}\n```\n\n## Routes file\n\nPara carregar um arquivo de rotas, utilize o método 'load'\n\n```php\n$app-\u003eload(__DIR__ . '/src/routes.xml');\n```\n\nroutes.xml\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\n\u003croutes\u003e\n    \u003croute\u003e\n        \u003cpath\u003e/\u003c/path\u003e\n        \u003cmethod\u003eGET\u003c/method\u003e\n        \u003ccallback\u003eApp\\Callback\\Page\\Home\u003c/callback\u003e\n    \u003c/route\u003e\n\n    \u003croute\u003e\n        \u003cpath\u003e/me\u003c/path\u003e\n        \u003cmethod\u003eGET\u003c/method\u003e\n        \u003ccallback\u003eApp\\Callback\\Page\\Me\u003c/callback\u003e\n        \u003cmiddlewares\u003e\n            \u003cmiddleware\u003eApp\\Middleware\\Token\\Ensure\u003c/middleware\u003e\n        \u003c/middlewares\u003e\n    \u003c/route\u003e\n\n    \u003croute\u003e\n        \u003cpath\u003e/dashboard\u003c/path\u003e\n        \u003cmethod\u003eGET\u003c/method\u003e\n        \u003ccallback\u003eApp\\Callback\\Page\\Dashboard\u003c/callback\u003e\n        \u003cmiddlewares\u003e\n            \u003cmiddleware\u003eApp\\Middleware\\Token\\Ensure\u003c/middleware\u003e\n            \u003cmiddleware\u003eApp\\Middleware\\Role::admin\u003c/middleware\u003e\n        \u003c/middlewares\u003e\n    \u003c/route\u003e\n\u003c/routes\u003e\n```\n\n# Middleware\n\nMiddlewares devem ser informados no terceiro parâmetro da rota ( [Routes](#routes) )\n\nPara configurar um middleware **global**, utilize o método 'use'\n\n```php\n$app-\u003euse('App\\Middleware\\A');\n$app-\u003euse('App\\Middleware\\B');\n```\n\nVeja abaixo alguns **exemplos** de middlewares\n\n## Assert Middleware\n\nMiddleware global que faz proteção contra CSRF e decodifica o payload do JWT\n\n*CSRF*\n\nOs formulários deverão ter um campo 'hidden' chamado 'csrf'\n\n```html\n\u003cinput type=\"hidden\" name=\"csrf\" value=\"{{ $csrf }}\"\u003e\n```\n\n\u003e O uso do CSRF necessita do `session_start();` no index.php\n\n*JWT*\n\n1 ) Se o token existir mas for inválido:\n\n[ Web ] Redireciona para a página 'login'\n\n[ API ] Retorna 'Invalid or expired token'\n\nCaso for válido, o payload será enviado para o próximo middleware ou controller, podendo ser recuperado usando `$req-\u003equery()` ( veja um exemplo em [Role Middleware](#role-middleware) )\n\n2 ) Se não existir, vai para o próximo middleware ou executa o controller\n\n```php\n\u003c?php\n\nnamespace App\\Middleware\\Token;\n\nuse Nano\\Core\\Security\\{ CSRF, JWT };\n\nclass Assert\n{\n    public function handle($req, $res)\n    {\n        CSRF::assert($req, $res);\n        JWT::assert($req, $res, '/login');\n    }\n}\n```\n\n\u003e O terceiro parâmetro em `JWT::assert` é ignorado em rotas de api\n\n## Ensure Middleware\n\nSerá chamado em rotas onde a autenticação é obrigatória\n\nSe não encontrar o token:\n\n[ Web ] Redireciona para a página 'login'\n\n[ API ] Retorna 'Authorization token not found in request'\n\n```php\n\u003c?php\n\nnamespace App\\Middleware\\Token;\n\nuse Nano\\Core\\Security\\JWT;\n\nclass Ensure\n{\n    public function handle($req, $res)\n    {\n        JWT::ensure($req, $res, '/login');\n    }\n}\n```\n\nA criação dos middlewares Assert e Ensure, obriga que as **rotas de api**, tenham o prefixo '/api/' para evitar redirecionamento\n\n\u003e O terceiro parâmetro em `JWT::ensure` é ignorado em rotas de api\n\n## Role Middleware\n\nSerá chamado em rotas onde o usuário precisa ter níveis de acesso específicos\n\n\u003e Coloque após o 'Ensure'\n\n```php\n$app-\u003eget(\n    '/dashboard',\n    'App\\Callback\\Page\\Dashboard',\n    ['App\\Middleware\\Token\\Ensure', 'App\\Middleware\\Token\\Role::admin']\n);\n```\n\n```php\n\u003c?php\n\nnamespace App\\Middleware;\n\nuse App\\Service\\Auth\\Role as Service;\nuse Exception;\n\nclass Role\n{\n    public function handle($req, $res, $args)\n    {\n        try {\n            $id = $req-\u003equery()-\u003edata-\u003eid;\n\n            $role = new Service()-\u003egetRoleByUserId($id);\n\n            if (!in_array($role, $args)) {\n                $res-\u003eredirect('/me');\n            }\n        } catch (Exception $e) {\n            throw new Exception($e-\u003egetMessage());\n        }\n    }\n}\n```\n\n# Query params\n\n\u003e localhost:8080/books?filter=price\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Page;\n\nuse App\\Service\\Book\\Read as Service;\nuse Exception;\n\nclass Book\n{\n    public function handle($req, $res)\n    {\n        try {\n            $filter = $req-\u003equery()-\u003efilter ?? null;\n\n            $res-\u003eview('books', array('books' =\u003e new Service()-\u003eall($filter)));\n        } catch (Exception $e) {\n            throw new Exception($e-\u003egetMessage());\n        }\n    }\n}\n```\n\n# cURL\n\nVerbos: GET, POST, PUT, PATCH e DELETE\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Payment;\n\nuse Nano\\Core\\Error;\nuse Exception;\n\nclass Create\n{\n    public function handle($req, $res)\n    {\n        try {\n            $url = 'https://...';\n\n            $headers = [\n                'Content-Type: application/json',\n                'Authorization: Bearer TOKEN'\n            ];\n\n            $body = json_encode(array(...));\n\n            $data = $req-\u003ehttp()-\u003epost($url, $headers, $body);\n\n            if (!$data) {\n                throw new Exception('Erro ao realizar requisição');\n            }\n\n            $info = json_decode($data);\n\n            $res-\u003ejson($info-\u003estatus, array('message' =\u003e $info-\u003emessage));\n        } catch (Exception $e) {\n            Error::throwJsonException(500, $e-\u003egetMessage());\n        }\n    }\n}\n```\n\n# Validator\n\nRegras: required, string, integer, float, bool, email, confirmed, min e max\n\n\u003e Caso não houver erros na validação, um novo objeto será retornado em `$req-\u003edata()` com os dados [sanitizados](https://github.com/jonathansilva/nano/blob/master/src/Core/Security/Sanitize.php)\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Book;\n\nuse App\\Service\\Book\\Create as Service;\nuse Nano\\Core\\Error;\nuse Exception;\n\nclass Create\n{\n    public function handle($req, $res)\n    {\n        try {\n            $rules = [\n                'title' =\u003e 'required|string',\n                'description' =\u003e 'required|string|max:255',\n                'authors' =\u003e [\n                    'name' =\u003e 'required|string',\n                    'website' =\u003e 'string'\n                ]\n            ];\n\n            $req-\u003evalidate($rules);\n\n            $data = $req-\u003edata();\n\n            $res-\u003ejson(201, array('message' =\u003e new Service()-\u003eregister($data)));\n        } catch (Exception $e) {\n            Error::throwJsonException(500, $e-\u003egetMessage());\n        }\n    }\n}\n```\n\n```php\n$rules = [\n    'password' =\u003e 'required|string|min:8|confirmed'\n];\n```\n\nO uso do `confirmed` exige um novo input, onde o 'name' precisa ter o sufixo '_confirmation'\n\n```html\n\u003cdiv class=\"field\"\u003e\n    \u003clabel for=\"password_confirmation\"\u003eConfirmar senha\u003c/label\u003e\n\n    \u003cinput type=\"password\" id=\"password_confirmation\" name=\"password_confirmation\" spellcheck=\"false\" autocomplete=\"off\"\u003e\n\u003c/div\u003e\n```\n\n\u003e Por padrão, as mensagens de erro estão em português. As opções aceitas são 'pt-BR' e 'en-US'\n\n```php\n$req-\u003evalidate($rules, 'en-US');\n```\n\n# JSON Exception\n\nUse `throwJsonException` para exibir erros no formato json\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Book;\n\nuse Nano\\Core\\Error;\nuse Exception;\n\nclass Read\n{\n    public function handle($req, $res)\n    {\n        try {\n            // TODO\n        } catch (Exception $e) {\n            Error::throwJsonException(500, $e-\u003egetMessage());\n        }\n    }\n}\n```\n\n# Template engine\n\nO template utilizado foi desenvolvido por David Adams ( https://codeshack.io )\n\n\u003e Foram feitas pequenas alterações no código original\n\nbase.html\n\n```html\n\u003c!DOCTYPE html\u003e\n\n\u003chtml lang=\"pt-BR\"\u003e\n    \u003chead\u003e\n        \u003ctitle\u003e{% yield title %}\u003c/title\u003e\n\n        \u003cmeta charset=\"UTF-8\"\u003e\n        \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\u003e\n        \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n\n        \u003clink rel=\"stylesheet\" href=\"/assets/style.css\"\u003e\n    \u003c/head\u003e\n\n    \u003cbody\u003e\n        {# comentário de teste #}\n        \u003cmain\u003e\n            {% yield content %}\n        \u003c/main\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nhome.html\n\n```html\n{% extends base %}\n\n{% block title %}Nano Framework{% endblock %}\n\n{% block content %}\n\u003ch1\u003e{{ $welcome }}\u003c/h1\u003e\n{% endblock %}\n```\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Page;\n\nclass Home\n{\n    public function handle($req, $res)\n    {\n        $res-\u003eview('home', array('welcome' =\u003e 'Welcome to Nano!'));\n    }\n}\n```\n\n\u003e Crie o diretório 'views'\n\nExibindo os erros de validação ( [Validator](#validator) )\n\n```html\n{% foreach ($errors as $value): %}\n    \u003cdiv\u003e{{ $value }}\u003c/div\u003e\n{% endforeach; %}\n```\n\nPara saber mais sobre este template engine, [clique aqui](https://codeshack.io/lightweight-template-engine-php)\n\n# CORS\n\nColoque no index.php de sua API e faça as modificações necessárias\n\n```php\nheader('Access-Control-Allow-Origin: *');\nheader('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE');\nheader('Access-Control-Allow-Headers: Origin, Accept, Content-Type, Authorization, X-Requested-With');\n```\n\n# Login / Register\n\n\u003e Exemplo de login e cadastro de usuário\n\nGET /cadastro\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Page;\n\nuse Nano\\Core\\View\\Form;\n\nclass Register\n{\n    public function handle($req, $res)\n    {\n        if ($req-\u003ehasCookie('token')) {\n            $res-\u003eredirect('/me');\n        }\n\n        $form = Form::session($req);\n\n        $res-\u003eview('register', [\n            'csrf' =\u003e $form-\u003ecsrf,\n            'errors' =\u003e $form-\u003eerrors\n        ]);\n    }\n}\n```\n\nPOST /cadastro\n\n\u003e Ao usar Cookie para salvar o JWT, nomeie-o de 'token'. Isso é necessário pois há funções na classe JWT, que *busca*, *verifica* e *remove* o cookie pelo nome 'token'\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\User;\n\nuse App\\Service\\User\\Create as Service;\nuse Nano\\Core\\Error;\nuse Exception;\n\nclass Create\n{\n    public function handle($req, $res)\n    {\n        try {\n            $rules = [\n                'name' =\u003e 'required|string',\n                'email' =\u003e 'required|email|confirmed',\n                'password' =\u003e 'required|string|min:8|confirmed'\n            ];\n\n            $req-\u003evalidate($rules);\n\n            $data = $req-\u003edata();\n\n            $req-\u003esetCookie('token', new Service()-\u003eregister($data));\n            $res-\u003eredirect('/me');\n        } catch (Exception $e) {\n            $req-\u003esetSession('errors', Error::parse($e-\u003egetMessage()));\n            $res-\u003eredirect('/cadastro');\n        }\n    }\n}\n```\n\nService\n\n```php\n\u003c?php\n\nnamespace App\\Service\\User;\n\nuse Nano\\Core\\Database;\nuse Nano\\Core\\Security\\JWT;\nuse Exception;\nuse PDO;\n\nclass Create\n{\n    private PDO $db;\n\n    public function __construct()\n    {\n        $this-\u003edb = Database::instance();\n    }\n\n    public function register(object $data): string\n    {\n        if ($this-\u003eemailExists($data-\u003eemail)) {\n            throw new Exception('O e-mail informado já existe');\n        }\n\n        $hash = password_hash($data-\u003epassword, PASSWORD_ARGON2ID);\n\n        try {\n            $query = \"INSERT INTO users (name, email, password) VALUES (:name, :email, :password)\";\n\n            $stmt = $this-\u003edb-\u003eprepare($query);\n            $stmt-\u003ebindValue(':name', $data-\u003ename, PDO::PARAM_STR);\n            $stmt-\u003ebindValue(':email', $data-\u003eemail, PDO::PARAM_STR);\n            $stmt-\u003ebindValue(':password', $hash, PDO::PARAM_STR);\n            $stmt-\u003eexecute();\n\n            return JWT::encode(array('id' =\u003e $this-\u003edb-\u003elastInsertId()));\n        } catch (PDOException $e) {\n            throw new Exception('Erro ao cadastrar, tente novamente');\n        }\n    }\n\n    private function emailExists(string $email): bool\n    {\n        $query = \"SELECT id FROM users WHERE email = :email LIMIT 1\";\n\n        $stmt = $this-\u003edb-\u003eprepare($query);\n        $stmt-\u003eexecute(array(':email' =\u003e $email));\n\n        return (bool) $stmt-\u003efetchColumn();\n    }\n}\n```\n\nGET /login\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Page;\n\nuse Nano\\Core\\View\\Form;\n\nclass Login\n{\n    public function handle($req, $res)\n    {\n        if ($req-\u003ehasCookie('token')) {\n            $res-\u003eredirect('/me');\n        }\n\n        $form = Form::session($req);\n\n        $res-\u003eview('login', [\n            'csrf' =\u003e $form-\u003ecsrf,\n            'errors' =\u003e $form-\u003eerrors\n        ]);\n    }\n}\n```\n\nPOST /login\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Auth;\n\nuse App\\Service\\Auth\\Login as Service;\nuse Nano\\Core\\Error;\nuse Exception;\n\nclass Login\n{\n    public function handle($req, $res)\n    {\n        try {\n            $rules = [\n                'email' =\u003e 'required|email',\n                'password' =\u003e 'required|string'\n            ];\n\n            $req-\u003evalidate($rules);\n\n            $data = $req-\u003edata();\n\n            $req-\u003esetCookie('token', new Service()-\u003eauthenticate($data));\n            $res-\u003eredirect('/me');\n        } catch (Exception $e) {\n            $req-\u003esetSession('errors', Error::parse($e-\u003egetMessage()));\n            $res-\u003eredirect('/login');\n        }\n    }\n}\n```\n\nService\n\n```php\n\u003c?php\n\nnamespace App\\Service\\Auth;\n\nuse Nano\\Core\\Database;\nuse Nano\\Core\\Security\\JWT;\nuse Exception;\nuse PDO;\n\nclass Login\n{\n    private PDO $db;\n\n    public function __construct()\n    {\n        $this-\u003edb = Database::instance();\n    }\n\n    public function authenticate(object $data): string\n    {\n        $query = \"SELECT id, password FROM users WHERE email = :email\";\n\n        $stmt = $this-\u003edb-\u003eprepare($query);\n        $stmt-\u003eexecute(array(':email' =\u003e $data-\u003eemail));\n\n        $result = $stmt-\u003efetchObject();\n\n        if (!password_verify($data-\u003epassword, $result-\u003epassword)) {\n            throw new Exception('E-mail ou senha inválido');\n        }\n\n        return JWT::encode(array('id' =\u003e $result-\u003eid));\n    }\n}\n```\n\nGET /logout\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Auth;\n\nclass Logout\n{\n    public function handle($req, $res)\n    {\n        if ($req-\u003ehasCookie('token')) {\n            $req-\u003eremoveCookie('token');\n            $res-\u003eredirect('/login');\n        }\n\n        $res-\u003eredirect('/');\n    }\n}\n```\n\nGET /me\n\n```php\n\u003c?php\n\nnamespace App\\Callback\\Page;\n\nuse App\\Service\\User\\Read as Service;\nuse Exception;\n\nclass Me\n{\n    public function handle($req, $res)\n    {\n        try {\n            $id = $req-\u003equery()-\u003edata-\u003eid;\n\n            $res-\u003eview('me', array('data' =\u003e new Service()-\u003egetUserInfoById($id)));\n        } catch (Exception $e) {\n            throw new Exception($e-\u003egetMessage());\n        }\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathansilva%2Fnano","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonathansilva%2Fnano","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathansilva%2Fnano/lists"}