{"id":21520753,"url":"https://github.com/salkuadrat/lucifer","last_synced_at":"2025-04-09T22:20:56.324Z","repository":{"id":56834271,"uuid":"422817520","full_name":"salkuadrat/lucifer","owner":"salkuadrat","description":"A fast, lightweight web framework in dart.","archived":false,"fork":false,"pushed_at":"2021-12-16T09:16:52.000Z","size":7216,"stargazers_count":24,"open_issues_count":2,"forks_count":7,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T00:13:51.476Z","etag":null,"topics":["dart","lucifer","web"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/lucifer","language":"Dart","has_issues":true,"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/salkuadrat.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-30T07:46:51.000Z","updated_at":"2023-09-15T15:59:20.000Z","dependencies_parsed_at":"2022-09-10T04:15:53.793Z","dependency_job_id":null,"html_url":"https://github.com/salkuadrat/lucifer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salkuadrat%2Flucifer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salkuadrat%2Flucifer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salkuadrat%2Flucifer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salkuadrat%2Flucifer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salkuadrat","download_url":"https://codeload.github.com/salkuadrat/lucifer/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248120059,"owners_count":21050886,"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":["dart","lucifer","web"],"created_at":"2024-11-24T01:03:55.180Z","updated_at":"2025-04-09T22:20:56.297Z","avatar_url":"https://github.com/salkuadrat.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lucifer Lightbringer\n\n\u003cimg src=\"https://github.com/salkuadrat/lucifer/raw/master/lucifer.png\" height=\"200\" alt=\"Lucifer\"\u003e\n\nLucifer is a fast, lightweight web framework in dart.\n\nBuilt on top of native dart `HttpServer` to provide an elegant way to fulfill the needs of many modern web server these days.\n\nLucifer is open, efficient, and provide lots of features to handle dozen kinds of things.\n\n## Installation \n\n[Install Dart SDK](https://dart.dev/get-dart)\n\nYou may start creating a new Lucifer project using lucy command.\n\n```bash\npub global activate lucy\n\nl create desire\n```\n\nThe first command will activate command-line interface (CLI), named [Lucy](https://pub.dev/packages/lucy), to be accessible from your terminal. \n\nThen `l create desire` will generate your new project in the `desire` directory. \n\nFeel free to use any project name you want.\n\n## Starting\n\nNow we are ready to play with our web server.\n\nYou may open `main.dart` in your project `lib` directory to learn the structure of a simple lucifer application.\n\n```dart\nimport 'package:lucifer/lucifer.dart';\n\nvoid main() {\n  final app = App();\n  final port = env('PORT') ?? 3000;\n\n  app.use(logger());\n\n  app.get('/', (Req req, Res res) async {\n    await res.send('Hello Detective');\n  });\n\n  await app.listen(port);\n  print('Server running at http://${app.host}:${app.port}');\n\n  app.checkRoutes();\n}\n```\n\nYou may test running it with the following command:\n\n```bash\ncd desire\nl run\n```\n\nNow you may open `http://localhost:3000` in the web browser. \n\nIf all went well, it will display `Hello Detective` and print the following message in your terminal.\n\n```text\nServer running at http://localhost:3000\n```\n\n## Fundamentals \n\nYou may learn the fundamentals of Lucifer by understanding the code inside the `lib/main.dart` of your new project.\n\nThe short lines of code do several things behind the scene.\n\nFirst, we import `lucifer` and create a web application by assigning a new `App` instance to `app`\n\n```dart\nimport 'package:lucifer/lucifer.dart';\n```\n\n```dart\nfinal app = App();\n```\n\nThen we set the server port with value `3000` from the `.env` file in your root project directory.\n\nYou may change it with any port you want.\n\n```dart\nfinal port = env('PORT') ?? 3000;\n```\n\nNext we tell it to listen to a GET request on root path `/` with `app.get()`\n\n```dart\napp.get('/', (Req req, Res res) async {\n  \n});\n```\n\nEvery HTTP verbs comes with its own method in Lucifer: `get()`, `post()`, `put()`, `patch()`, `delete()`, with the first argument corresponds to the route path.\n\n```dart\napp.get('/', (Req req, Res res) {\n\n});\n\napp.post('/', (Req req, Res res) {\n\n});\n\napp.put('/', (Req req, Res res) {\n\n});\n\napp.patch('/', (Req req, Res res) {\n\n});\n\napp.delete('/', (Req req, Res res) {\n\n});\n```\n\nFor the second argument, you may see a callback function that will be called when an incoming request is processed, and send a response with it.\n\nTo handle the incoming request and send a response, you may write your code inside the callback function.\n\n```dart\napp.get('/', (Req req, Res res) async {\n  await res.send('Hello Detective');\n});\n```\n\nIn the route callback function, Lucifer provides two objects, `req` and `res`, that represents `Req` and `Res` instance.\n\n`Req` is a Request class built on top of native dart `HttpRequest`. \n\nIt holds all information about the incoming request, such as request parameters, query string, headers, body, and more.\n\n`Res` is a Response class built on top of native dart `HttpResponse`. \n\nIt's mostly used to manipulate response and sending it to the client.\n\nWhat you did before is sending a message string `Hello Detective` to the client using `res.send()`. This method sets the string in the response body, and then close the connection.\n\nThe last line of our code starts the server and listen for incoming requests on the specified `port`:\n\n```dart\nawait app.listen(port);\nprint('Server running at http://${app.host}:${app.port}');\n```\n\nAs an alternative, you may also use `app.listen()` like so:\n\n```dart\n// listen to the specified port and host\nawait app.listen(port, '127.0.0.1');\n\n// listen to the specified port with callback\nawait app.listen(port, () {\n  print('Server running at http://${app.host}:${app.port}');\n});\n\n// listen to the specified port and host with callback\nawait app.listen(port, 'localhost', () {\n  print('Server running at http://${app.host}:${app.port}');\n});\n```\n\n## Environment Variables\n\nEnvironment is a set of variables known to a process (such as, ENV, PORT, etc). \n\nIt's highly recommended to mimic production environment during development by reading it from `.env` file.\n\nWhen we run `l create` command, a `.env` file is created in the root project directory, containing these values.\n\n```text\nENV = development\nPORT = 3000\n```\n\nThen you may access the `.env` value from your dart code using `env()` method:\n\n```dart\nvoid main() {\n  final app = App();\n  final port = env('PORT') ?? 3000; // get port from env\n\n  // get ENV value to check if it's a development or production stage\n  final environment = env('ENV'); \n\n  ...\n}\n```\n\nFor maximum security, we should always use environment variables for important things, such as database configurations and JSON Web Token (JWT) secret.\n\nAnd one more thing... you may open `.gitignore` file in the root directory, and see that `.env` is included there. \n\nIt means your `.env` file will not be uploaded to remote repository like GitHub, and your environment variables values will never be exposed to the public eyes.\n\n## Request Parameters \n\nWe've learned before that the `req` object holds the HTTP request informations. \n\nThere are some properties of `req` that you will likely access in your application.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eProperty\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eapp\u003c/td\u003e\n    \u003ctd\u003eholds reference to the Lucifer app object\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003euriString\u003c/td\u003e\n    \u003ctd\u003eURI string of the request\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003epath\u003c/td\u003e\n    \u003ctd\u003ethe URL path\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003emethod\u003c/td\u003e\n    \u003ctd\u003ethe HTTP method being used\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eparams\u003c/td\u003e\n    \u003ctd\u003ethe route named parameters\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003equery\u003c/td\u003e\n    \u003ctd\u003ea map object containing all the query string used in the request\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003ebody\u003c/td\u003e\n    \u003ctd\u003econtains the data submitted in the request body (must be parsed before you can access it)\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003ecookies\u003c/td\u003e\n    \u003ctd\u003econtains the cookies sent by the request (needs the `cookieParser` middleware)\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eprotocol\u003c/td\u003e\n    \u003ctd\u003eThe request protocol (http or https)\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003esecure\u003c/td\u003e\n    \u003ctd\u003etrue if request is secure (using HTTPS)\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## GET Query String\n\nNow you may learn how to retrieve the GET query parameters. \n\nQuery string is the part that comes after URL path, and starts with a question mark `?` like `?username=lucifer`. \n\nMultiple query parameters can be added with character `\u0026` like so:\n\n```text\n?username=lucifer\u0026age=10000\n```\n\nHow may we get the values?\n\nLucifer provides `req.query` object to make it easy to get all query values.\n\n```dart\napp.get('/', (Req req, Res res) {\n  print(req.query);\n});\n```\n\nThe `req.query` object contains map of each query parameter. If there are no query, it will be an empty map or `{}`.\n\nYou may iterate on it with for loop. The following code will print each query key and its value:\n\n```dart\nfor (var key in req.query.keys) {\n  var value = req.query[key];\n  print('Query $key: $value');\n}\n```\n\nYou may also access the individual value directly with `req.q()`\n\n```dart\nreq.q('username'); // same as req.query['username']\n\nreq.q('age'); // same as req.query['age']\n```\n\n## POST Request Data\n\nPOST request data are sent by HTTP clients, such as from HTML form, or from a POST request sent using Postman or from an AJAX JavaScript code.\n\nHow may we access these data?\n\nIf the request data is sent as json with `Content-Type: application/json`, you may use `json()` middleware.\n\n```dart\nfinal app = App();\n\n// use json middleware to parse json request body\n// usually sent from REST API\napp.use(json());\n// use xssClean to clean the inputs\napp.use(xssClean());\n```\n\nIf it's sent as urlencoded `Content-Type: application/x-www-form-urlencoded`, you may use `urlencoded()` middleware.\n\n```dart\nfinal app = App();\n\n// use urlencoded middleware to parse urlencoded request body\n// usually sent from HTML form\napp.use(urlencoded());\n// use xssClean to clean the inputs\napp.use(xssClean());\n```\n\nIt all went well, you may access the parsed request data from `req.body`:\n\n```dart\napp.post('/login', (Req req, Res res) {\n  final username = req.body['username'];\n  final password = req.body['password'];\n});\n```\n\nYou may also use `req.data()` to access an individual request data directly:\n\n```dart\napp.post('/login', (Req req, Res res) {\n  final username = req.data('username');\n  final password = req.data('password');\n});\n```\n\nBesides `json()` and `urlencoded()`, there are other available built in body parsers we can use. \n\n- `raw()` : to get request body as raw bytes\n- `text()` : to get request body as a plain string\n- `json()` : to parse json request body\n- `urlencoded()` : to parse urlencoded request body\n- `multipart()` : to parse multipart request body\n\nTo ensure the core framework stays lightweight, Lucifer will not assume anything about the request body. You may choose and apply the appropriate parser as needed in your application.\n\nHowever, if you want to be safe and need to be able to handle all forms of request body, you may simply use the all-inclusive `bodyParser()` middleware.\n\n```dart\nfinal app = App();\n\napp.use(bodyParser());\n```\n\nThe `bodyParser` middleware will automatically detect the type of request body, and use the appropriate parser accordingly for each of incoming request in your application.\n\n## Send Response \n\nIn the example above, we have used `res.send()` to send a simple response to the client.\n\n```dart\napp.get('/', (Req req, Res res) async {\n  await res.send('Hello Detective');\n});\n```\n\nIf you pass a string, lucifer will set `Content-Type` header to `text/html`.\n\nIf you pass a map or list object, it will set as `application/json`, and encode the data into JSON.\n\n`res.send()` will set the correct `Content-Length` response header automatically.\n\n`res.send()` also will close the connection when it's all done.\n\nYou may use `res.end()` method to send an empty response without any content in the response body.\n\n```dart\napp.get('/', (Req req, Res res) async {\n  await res.end();\n});\n```\n\nAnother thing is you may also send the data directly without `res.send()` like so:\n\n```dart\napp.get('/string', (req, res) =\u003e 'string');\n\napp.get('/int', (req, res) =\u003e 25);\n\napp.get('/double', (req, res) =\u003e 3.14);\n\napp.get('/json', (req, res) =\u003e { 'name': 'lucifer' });\n\napp.get('/list', (req, res) =\u003e ['Lucifer',  'Detective']);\n```\n\n## HTTP Status Response\n\nYou may set the HTTP status response using `res.status()` method.\n\n```dart\nres.status(404).end();\n```\n\nor \n\n```dart\nres.status(404).send('Not Found');\n```\n\nOr you may simply use `res.sendStatus()` for a shortcut.\n\n```dart\n// shortcut for res.status(200).send('OK');\nres.sendStatus(200); \n\n// shortcut for res.status(403).send('Forbidden');\nres.sendStatus(403);\n\n// shortcut for res.status(404).send('Not Found');\nres.sendStatus(404);\n\n// shortcut for res.status(500).send('Internal Server Error');\nres.sendStatus(500);\n```\n\n## JSON Response\n\nBesides `res.send()` method, we may also use `res.json()` to send json data to the client. \n\nThe method accepts a map or list object, and automatically encode it into json string with `jsonEncode()`\n\n```dart\nres.json({ 'name': 'Lucifer', 'age': 10000 });\n```\n\n```dart\nres.json(['Lucifer', 'Detective', 'Amenadiel']);\n```\n\n## Cookies \n\nYou may use `res.cookie()` to manage cookies in your application.\n\n```dart\nres.cookie('username', 'Lucifer');\n```\n\nThe method accepts additional parameters with various options.\n\n```dart\nres.cookie(\n  'username', \n  'Lucifer', \n  domain: '.luciferinheaven.com',\n  path: '/admin',\n  secure: true,\n);\n```\n\n```dart\nres.cookie(\n  'username',\n  'Lucifer',\n  expires: Duration(milliseconds: DateTime.now().millisecondsSinceEpoch + 900000),\n  httpOnly: true,\n);\n```\n\nHere is some cookie parameters you may eat.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eValue\u003c/th\u003e\n    \u003cth\u003eType\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003edomain\u003c/td\u003e\n    \u003ctd\u003eString\u003c/td\u003e\n    \u003ctd\u003eDomain name for the cookie. Defaults to the domain name of the app\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003eexpires\u003c/td\u003e\n    \u003ctd\u003eDate\u003c/td\u003e\n    \u003ctd\u003eExpiry date of the cookie in GMT. If not specified or set to 0, creates a session cookie that will be deleted when client close the browser.\n\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003ehttpOnly\u003c/td\u003e\n    \u003ctd\u003ebool\u003c/td\u003e\n    \u003ctd\u003eFlags the cookie to be accessible only by the web server\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003emaxAge\u003c/td\u003e\n    \u003ctd\u003eint\u003c/td\u003e\n    \u003ctd\u003eConvenient option for setting the expiry time relative to the current time in milliseconds\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003epath\u003c/td\u003e\n    \u003ctd\u003eString\u003c/td\u003e\n    \u003ctd\u003ePath for the cookie. Defaults to /\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003esecure\u003c/td\u003e\n    \u003ctd\u003ebool\u003c/td\u003e\n    \u003ctd\u003eMarks the cookie to be used with HTTPS only\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003esigned\u003c/td\u003e\n    \u003ctd\u003ebool\u003c/td\u003e\n    \u003ctd\u003eIndicates if the cookie should be signed\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003esameSite\u003c/td\u003e\n    \u003ctd\u003ebool or String\u003c/td\u003e\n    \u003ctd\u003eSet the value of SameSite cookie\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nA cookie can be deleted with\n\n```dart\nres.clearCookie('username');\n```\n\nOr you may use the following code to clear all cookies.\n\n```dart\nres.clearCookies();\n```\n\n## Secure Cookies\n\nYou may secure cookies in your application using `secureCookie()` middleware.\n\n```dart\nString cookieSecret = env('COOKIE_SECRET_KEY');\n\napp.use(secureCookie(cookieSecret));\n```\n\n`COOKIE_SECRET_KEY` needs to be set in the `.env` file and should be a random string unique to your application.\n\n## HTTP Headers\n\nYou may get the HTTP header of a request from `req.headers`\n\n```dart\napp.get('/', (req, res) {\n  print(req.headers);\n});\n```\n\nYou may also use `req.get()` or `req.header()` to get an individual header value.\n\n```dart\napp.get('/', (req, res) {\n  final userAgent = req.get('User-Agent');\n\n  // same as \n\n  req.header('User-Agent');\n});\n```\n\nTo change the HTTP header of a response to client, you may use use `res.set()` and `res.header()`\n\n```dart\nres.set('Content-Type', 'text/html');\n\n// same as \n\nres.header('Content-Type', 'text/html');\n```\n\nHere are some other ways to modify the Content-Type header of a response to the clinet.\n\n```dart\nres.type('.html'); // res.set('Content-Type', 'text/html');\n\nres.type('html'); // res.set('Content-Type', 'text/html');\n\nres.type('json'); // res.set('Content-Type', 'application/json');\n\nres.type('application/json'); // res.set('Content-Type', 'application/json');\n\nres.type('png'); // res.set('Content-Type', 'image/png');\n```\n\n## Redirects\n\nUsing redirects are common thing to do in a web application. \n\nYou may redirect a response in your application with `res.redirect()` or `res.to()`\n\n```dart\nres.redirect('/get-over-here');\n\n// same as \n\nres.to('/get-over-here');\n```\n\nIt will create redirect with the default 302 status code.\n\nYou may also use it this way to set a custom status code.\n\n```dart\nres.redirect(301, '/get-over-here');\n\n// same as \n\nres.to(301, '/get-over-here');\n```\n\nYou may pass the path to `res.redirect()` with an absolute path (`/get-over-here`), an absolute URL (`https://scorpio.com/get-over-here`), a relative path (`get-over-here`), or `..` to go back one level.\n\n```dart\nres.redirect('../get-over-here');\n\nres.redirect('..');\n```\n\nOr you may simply use `res.back()` to redirect back to the previous url, based on the HTTP Referer value sent by client in the request header (defaults to / if it's not set).\n\n```dart\nres.back();\n```\n\n## Routing \n\nRouting is the process of determining what should happen when a URL is called, and which parts of the application needs to handle the request.\n\nIn the example before we have used routing like so:\n\n```dart\napp.get('/', (req, res) async {\n\n});\n```\n\nThe code above creates a route that maps a root path `/` with HTTP GET method to the response we provide inside the callback function.\n\nWe may use named parameters to listen for custom request. \n\nSay we want to provide a profile API that accepts a string as username, and return the user details. \n\nHowever, we want the string parameter to be part of the URL, not as a query string. \n\nSo we use the named parameters like so:\n\n```dart\napp.get('/profile/:username', (Req req, Res res) {\n  // get username from URL parameter\n  final username = req.params['username'];\n\n  print(username);\n});\n```\n\nYou may use multiple parameters in the same URL, then it will be included automatically to the `req.params` values.\n\nYou may also use `req.param()` to access an individual value of `req.params`\n\n```dart\napp.get('/profile/:username', (Req req, Res res) {\n  // get username from URL parameter\n  final username = req.param('username');\n\n  print(username);\n});\n```\n\n\u003c!---\nUse regular expression to match multiple paths with one statement.\n\n```dart\napp.get(RegExp(/post/), (Req req, Res res) {\n\n});\n```\n\nThe regex route above will match every requests that contains string `post`, such as `/post`, `/post/first`, `/thepost`, `/shitposting/anything` and so on.\n\n--\u003e\n\n## Advanced Routing\n\nWe may use `Router` object from `app.router()` to build an organized routing.\n\n```dart\nfinal app = App();\nfinal router = app.router();\n\nrouter.get('/login', (req, res) async {\n  await res.send('Login Page');\n});\n\napp.use('/auth', router);\n```\n\nYou may run the code above, and the login page will be available at http://localhost:3000/auth/login.\n\nYou may register as many routers as you need.\n\n```dart\nfinal app = App();\n\nfinal auth = app.router();\nfinal user = app.router();\n\n// register routes for auth\n\nauth.get('/login', (Req req, Res res) async {\n  await res.send('Login Page');\n});\n\nauth.post('/login', (Req req, Res res) async {\n  // process POST login\n});\n\nauth.get('/logout', (Req req, Res res) async {\n  // process logout\n});\n\n// register routes for user\n\nuser.get('/', (Req req, Res res) async {\n  await res.send('List User');\n});\n\nuser.get('/:id', (Req req, Res res) async {\n  final id = req.param('id');\n  await res.send('Profile $id');\n});\n\nuser.post('/', (Req req, Res res) async {  \n  // create user\n});\n\nuser.put('/:id', (Req req, Res res) async {\n  // edit user by id\n});\n\nuser.delete('/', (Req req, Res res) async {\n  // delete all users\n});\n\nuser.delete(':id', (Req req, Res res) async {\n  // delete user\n});\n\n// apply the router\napp.use('/auth', auth);\napp.use('/user', user);\n```\n\nUsing `app.router()` is a good practice to organize your endpoints. \n\nYou may split them into some independent files to maintain a clean, structured and easy-to-read code.\n\nAnother way to organize your app is using `app.route()`\n\n```dart\nfinal app = App();\n\napp.route('/user')\n  .get('/', (Req req, Res res) async {\n    await res.send('List User');\n  })\n  .get('/:id', (Req req, Res res) async {\n    final id = req.param('id');\n    await res.send('Profile $id');\n  })\n  .post('/', (Req req, Res res) async {\n    // create user\n  })\n  .put('/:id', (Req req, Res res) async {\n    // edit user by id\n  })\n  .delete('/', (Req req, Res res) async {\n    // delete all users\n  })\n  .delete('/:id', (Req req, Res res) async {\n    // delete user\n  });\n```\n\nAnother way to use `app.route()` is by utilizing class `Controller`. \n\nThis is useful especially when you are building a REST API.\n\nYou may create a new controller in the `/lib/controller` directory.\n\n```dart\nclass UserController extends Controller {\n  UserController(App app) : super(app);\n\n  @override\n  FutureOr index(Req req, Res res) async {\n    await res.send('User List');\n  }\n\n  @override\n  FutureOr view(Req req, Res res) async {\n    await res.send('User Detail');\n  }\n\n  @override\n  FutureOr create(Req req, Res res) async {\n    await res.send('Create User');\n  }\n\n  @override\n  FutureOr edit(Req req, Res res) async {\n    await res.send('Edit User');\n  }\n\n  @override\n  FutureOr delete(Req req, Res res) async {\n    await res.send('Delete User');\n  }\n\n  @override\n  FutureOr deleteAll(Req req, Res res) async {\n    await res.send('Delete All Users');\n  }\n}\n```\n\nThen use it in your main app like so.\n\n```dart\nfinal app = App();\nfinal user = UserController(app);\n\n// This will add all associated routes for all methods\napp.route('/user', user);\n\n// The 1-line code above is the same as \n// manually adding these yourself\napp.route('/user')\n  .get('/', user.index)\n  .post('/', user.create)\n  .delete('/', user.deleteAll)\n  .get('/:id', user.view)\n  .put('/:id', user.edit)\n  .delete('/:id', user.delete);\n```\n\nIt's a good practice to split your routes into its own independent controllers.\n\nYou may also add more methods to your `Controller`\n\n```dart\nclass UserController extends Controller {\n\n  ...\n\n  FutureOr vip(Req req, Res res) async {\n    await res.send('List of VIP Users');\n  }\n}\n```\n\nAnd apply the method by chaining `app.route()`\n\n```dart\nfinal app = App();\nfinal user = UserController(app);\n\n// this will add route GET /user/vip into your app\n// along with all the standard routes above\napp.route('/user', user).get('/vip', user.vip);\n```\n\nTo help you with adding `Controller` to your project, Lucifer provides another command.\n\n```shell\n$ l c post\n```\n\nThe command above will create file `post_controller.dart` in the `/lib/controller` directory, and fill it with a boilerplate `PostController` class.\n\nYou may also use the command to create multiple `Controller`.\n\n```shell\n$ l c post news user customer\n```\n\n## Static Files\n\nIt's common to have images, css, and javascripts in a public folder.\n\nYou may expose them by using `static()` middleware.\n\n```dart\nfinal app = App();\n\napp.use(static('public'));\n```\n\nNow if you have `index.html` file in the `public` directory, it will be served automatically when you hit `http://localhost:3000`.\n\n## Sending Files\n\nLucifer provides a simple way to send file to the client with `res.download()` or `res.sendFile()`.\n\nWhen user hit a route that sends file with `res.download()`, browsers will prompt the user for download. \n\nInstead of showing it in the browser, the file will be saved to the local drive.\n\n```dart\napp.get('/downloadfile', (Req req, Res res) async {\n  await res.download('thefile.pdf');\n\n  // same as\n\n  await res.sendFile('thefile.pdf');\n});\n```\n\nYou may send file with a custom filename.\n\n```dart\napp.get('/downloadfile', (Req req, Res res) async {\n  await res.download('thefile.pdf', 'File.pdf');\n});\n```\n\nAnd use the following to handle error during the process of sending file.\n\n```dart\napp.get('/downloadfile', (Req req, Res res) async {\n  final err = await res.download('./thefile.pdf', 'File.pdf');\n\n  if (err != null) {\n    // handle error\n  }\n});\n```\n\n## CORS \n\nA client app running in the browser usually can only access resources from the same domain (origin) as the server.\n\nLoading images or scripts/styles usually works, but XHR and Fetch calls to another server will fail, unless the server implements a way to allow that connection.\n\nThat way is CORS (Cross-Origin Resource Sharing).\n\nLoading web fonts using `@font-face` also has same-origin-policy by default, and also other less popular things (like WebGL textures).\n\nIf we don't set up a CORS policy that allows 3rd party origins, the requests will fail.\n\nA cross origin request fail if it's sent\n\n- to a different domain \n- to a different subdomain\n- to a different port\n- to a different protocol\n\nCORS exists for your own security... to prevent any malicious users from exploiting your resources.\n\nBut if you control both the server and client, it's assumed to be safe to allow them to talk with each other.\n\nYou may use `cors` middleware to set up the CORS policy.\n\nAs an example, lets say you have a simple route without cors.\n\n```dart\nfinal app = App();\n\napp.get('/no-cors', (Req req, Res res) async {\n  await res.send('Risky without CORS');\n});\n```\n\nWhen you hit `/no-cors` using fetch request from a different origin, it will raise a CORS issue.\n\nAll you need to make it work is using the built in `cors` middleware and pass it to the request handler.\n\n```dart\nfinal app = App();\n\napp.get('/yes-cors', cors(), (Req req, Res res) async {\n  await res.send('Now it works');\n});\n```\n\nYou may apply `cors` for all incoming requests by using `app.use()`\n\n```dart\nfinal app = App();\n\napp.use(cors());\n\napp.get('/', (Req req, Res res) async {\n  await res.send('Now all routes will use cors');\n});\n```\n\nBy default, cors will set cross-origin header to accept any incoming requests. You may change it to only allow one origin and block all the others.\n\n```dart\nfinal app = App();\n\napp.use(cors(\n  origin: 'https://luciferinheaven.com'\n));\n\napp.get('/', (Req req, Res res) async {\n  await res.send('Now all routes can only accept request from https://luciferinheaven.com');\n});\n```\n\nYou may also set cors to allow multiple origins.\n\n```dart\nfinal app = App();\n\napp.use(cors(\n  origin: [\n    'https://yourfirstdomain.com',\n    'https://yourseconddomain.com',\n  ],\n));\n\napp.get('/', (Req req, Res res) async {\n  await res.send('Now all routes can accept request from both origins');\n});\n```\n\n## Session\n\nWe need to use sessions to identify client across the incoming requests.\n\nBy default, HTTP requests are stateless, sequential and two requests can't be linked to each other. \n\nThere is no way to know if a request comes from a client that has already performed another request.\n\nUsers can't be identified unless we use some kind of magic that makes it possible.\n\nThis is what sessions are (JSON Web Token is another).\n\nWhen handled correctly, each user of your application will be assigned to a unique session ID, and it allows you to store the user state.\n\nYou may use the built-in `session` middleware.\n\n```dart\nfinal app = App();\n\napp.use(session(secret: 'super-s3cr3t-key'));\n```\n\nAnd now all requests in your application will use session.\n\n`secret` is the only required parameter, but there are many more you can use.\n\n`secret` should use a random string, unique to your application (or generate it from [randomkeygen](https://randomkeygen.com/)).\n\nThis session is now active and attached to the request. \n\nYou may access it using `req.session()`\n\n```dart\napp.get('/', (Req req, Res res) {\n  print(req.session()); // print all session values\n});\n```\n\nTo get a specific value from the session, you may use `req.session(name)`\n\n```dart\nfinal username = req.session('username');\n```\n\nYou may use `req.session(name, value)` to add (or replace) value in the session.\n\n```dart\nfinal username = 'lucifer';\n\nreq.session('username', username);\n```\n\nSessions can be used to communicate data between middlewares, or retrieve it later in the next request.\n\nWhere do we store this session? Well, it depends on the set up that we use for our sessions.\n\nIt can be stored in:\n\n- memory: this is the default, but don't use it in production\n- database: like Postgres, SQLite, MySQL or MongoDB\n- memory cache: like Redis or Memcached\n\nAll the session store above will only set session ID in a cookie, and keep the real data server-side. \n\nClients will receive this session id, and send it back in each of their next requests. \n\nThen the server can use it to get the data associated with these session.\n\nMemory is the default setting for session. It's simple and needs zero setup on your part. \n\nHowever, it's not recommended for production. \n\nThe most efficient is using memory cache like Redis, but it needs some efforts on your part to set up the infrastructure.\n\n## JSON Web Token\n\nJSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. \n\nThis information can be verified and trusted because it is digitally signed. \n\nJWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.\n\nYou may utilize JWT in your Lucifer application by using an instance of `Jwt`  to sign and verify token.\n\n```dart\nfinal app = App();\nfinal port = env('PORT') ?? 3000;\n\nfinal jwt = Jwt();\n\n// Don't forget to put your jwt secret in environment variables\nfinal secret = env('JWT_SECRET');\n\napp.get('/login', (Req req, Res res) {\n\n  ...\n\n  final payload = \u003cString, dynamic\u003e{\n    'username': 'lucifer',\n    'age': 10000,\n  };\n\n  final token = jwt.sign(\n    payload, \n    secret, \n    expiresIn: Duration(seconds: 86400),\n  );\n\n  // Send token to the client by putting it  \n  // into 'x-access-token' header\n  res.header('x-access-token', token);\n\n  ...\n\n});\n```\n\nYou may use `jwt.verify()` to verify the token.\n\n```dart\nfinal app = App();\nfinal port = env('PORT') ?? 3000;\n\nfinal jwt = Jwt();\nfinal secret = env('JWT_SECRET');\n\napp.get('/', (Req req, Res res) {\n  // Get token from 'x-access-token' header\n  final token = req.header('x-access-token');\n\n  try {\n    final data = jwt.verify(token, secret);\n\n    if (data != null) {\n      print(data['username']);\n    }\n  } on JWTExpiredError {\n    // handle JWTExpiredError\n  } on JWTError catch (e) {\n    // handle JWTError\n  } on Exception catch (e) {\n    // handle Exception\n  }\n\n  ...\n\n});\n```\n\nAnother way to verify the token.\n\n```dart\nfinal app = App();\nfinal port = env('PORT') ?? 3000;\n\nfinal jwt = Jwt();\nfinal secret = env('JWT_SECRET');\n\napp.get('/', (Req req, Res res) {\n  // Get token from client 'x-access-token' header\n  final token = req.header('x-access-token');\n\n  jwt.verify(token, secret, (error, data) {\n    if (data != null) {\n      print(data['username']);\n    }\n\n    if (error != null) {\n      print(error);\n    }\n  });\n\n  ...\n\n});\n```\n\n## Middleware\n\nMiddleware is a function that hooks into the routing process. It performs some operations before executing the route callback handler.\n\nMiddleware is usually used to modify the req or res object, or to terminate the request before it reaches route callback.\n\nYou may add middleware in your Lucifer application like so:\n\n```dart\napp.use((Req req, Res res) async {\n  // do something\n});\n```\n\nThe code looks similar with the route callback.\n\nMost of the time, you will be enough with using the built-in Lucifer middlewares, like `static`, `cors`, or `session`.\n\nHowever, you may create a custom middleware, and then use it for a specific route by putting it in the middle of route and callback.\n\n```dart\nfinal app = App();\n\n// create custom middleware\nfinal custom = (Req req, Res res) async {\n  // do something here\n};\n\n// use the middleware for GET / request\napp.get('/', custom, (Req req, Res res) async {\n  await res.send('angels');\n});\n```\n\nYou may apply multiple middlewares to the route you want.\n\n```dart\nfinal app = App();\n\nfinal verifyToken = (Req req, Res res) async {\n  // do something here\n};\n\nfinal authorize = (Req req, Res res) async {\n  // do something here\n};\n\napp.get('/user', [ verifyToken, authorize ], (req, res) async {\n  await res.send('angels');\n});\n```\n\nIf you need to pass data from a middleware to be accessible at the next middlewares or the route callback, you may use `res.local()`\n\n```dart\nfinal app = App();\n\nfinal verifyToken = (Req req, Res res) async {\n  // saving token into the local data\n  res.local('token', 'jwt-token');\n};\n\nfinal authorize = (Req req, Res res) async {\n  // get token from local data\n  var token = res.local('token');\n};\n\napp.get('/user', [ verifyToken, authorize ], (req, res) async {\n  // get token from local data\n  var token = res.local('token');\n});\n```\n\nThere is no `next()` to call in these middleware (unlike other web frameworks). \n\nProcessing next is automatically handled by lucifer.\n\nLucifer will always run to the next middleware or callback in the current stack... Unless, you send some response to the client in the middleware, which will close the connection and stop all executions of the next middlewares/callback.\n\nSince the call is automatic, it's important to always remember to use proper  `async` `await` when you're dealing with asynchronous functions.\n\nAs an example, remember to use `async` `await` when using `res.download()` to send a file to the client:\n\n```dart\napp.get('/download', (Req req, Res res) async {\n  await res.download('somefile.pdf');\n});\n```\n\nHere is a simple rule to follow... if calling a function that returns `Future` or `FutureOr`, you may be better to play safe and use `async` `await`\n\nIf in the middle of debugging your application, you see error in the terminal with messages like `HTTP headers not mutable` or `headers already sent`, it's a clear indicator that some parts in the application need to use proper `async await`.\n\nTo help you with adding custom middleware to your project, Lucifer provides another command like so:\n\n```shell\n$ l m custom\n```\n\nThe command above will create file `custom.dart` in the `/lib/middleware` directory, and fill it with a boilerplate `custom` middleware function.\n\nYou may also use the command to generate multiple middlewares.\n\n```shell\n$ l m custom log auth\n```\n\n## Forms\n\nSay we have an HTML form like so:\n\n```html\n\u003cform method=\"POST\" action=\"/login\"\u003e\n  \u003cinput type=\"text\" name=\"username\" /\u003e\n  \u003cinput type=\"password\" name=\"password\" /\u003e\n  \u003cinput type=\"submit\" value=\"Login\" /\u003e\n\u003c/form\u003e\n```\n\nWhen user press the submit button, browser will automatically make a POST request to `/login`, and with it, sending some data to the server encoded as `application/x-www-form-urlencoded`. \n\nIn this case, the POST data contains `username` and `password`.\n\nForm may also send data with GET method, but mostly it will use the standard POST.\n\nThe data will be attached in the request body. To extract it, you may use the built in `urlencoded` middleware.\n\n```dart\nfinal app = App();\n\napp.use(urlencoded());\n// always use xssClean to clean the inputs\napp.use(xssClean());\n```\n\nYou may test creating a POST endpoint for `/login`, and the submitted data will be available at `req.body`. \n\n```dart\napp.post('/login', (Req req, Res res) async {\n  final username = req.body['username']; // same as req.data('username');\n  final password = req.body['password']; // same as req.data('password');\n\n  ...\n});\n```\n\nYou may also use `req.data()` to access an individual value of the form data.\n\n```dart\napp.post('/login', (Req req, Res res) async {\n  final username = req.data('username');\n  final password = req.data('password');\n\n  ...\n});\n```\n\n\u003c!---\n\nNow you may learn how to validate and sanitize the form data using `check` middleware.\n\n## Input Validation\n\nSay you have a POST endpoint that accepts name, email and age.\n\n```dart\nfinal app = App();\n\napp.use(urlencoded());\n// always use xssClean to clean the inputs\napp.use(xssClean());\n\napp.post('/user', (Req req, Res res) async {\n  final name = req.data('name');\n  final email = req.data('email');\n  final age = req.data('age');\n\n  ...\n\n});\n```\n\nHow to validate those results (server-side) to make sure:\n\n- name is a string with at least 3 characters?\n- email is a valid email?\n- age is a number between 0 and 150?\n\nThe easy way to do this is using `check` middleware.\n\n```dart\nfinal app = App();\nfinal check = app.check();\n\nfinal validations = [\n  check('name').isLength({ min: 3}),\n  check('email').isEmail(),\n  check('age').isNumeric(),\n];\n\napp.post('/user', validations, (Req req, Res res) async {\n  final name = req.data('name');\n  final email = req.data('email');\n  final age = req.data('age');\n\n  ...\n\n});\n```\n\nBeside those three `isLength`, `isEmail` and `isNumeric`, there are other methods we can use.\n\n- `contains()`: check if the data contains a specified value\n- `equals()`: check if the data equals a specified value\n- `isAlpha()`\n- `isAlphanumeric()`\n- `isAscii()`\n- `isBase64()`\n- `isBoolean()`\n- `isCurrency()`\n- `isDecimal()`\n- `isEmpty()`\n- `isFloat()`\n- `isHash()`\n- `isHexColor()`\n- `isIP()`\n- `isInt()`\n- `isJSON()`\n- `isLatLong()`\n- `isLowercase()`\n- `isMobilePhone()`\n- `isPostalCode()`\n- `isURL()`\n- `isUppercase()`\n- `isWhitelisted()`: theck if the data is in a whitelist of allowed characters\n- `isIn()`: check if the data is in array of specified values\n- `isFQDN()`: is a fully qualified domain name\n\nYou may also validate the input with regular expression by using `matches()`.\n\nDates can be checked with \n\n- `isAfter()`: check if the entered date is after the one you pass \n- `isBefore()`: check if the entered date is before the one you pass\n- `isISO8601()`\n- `isRFC3339()`\n\nAll those validations can be combined like this.\n\n```dart\ncheck('name').isAlpha().isLength({ min: 10 })\n```\n\nIf there is an error in validation, the server will automatically send it along with the response to the client. For example, if email is not valid, error message will be sent.\n\n```json\n{\n  \"errors\": [\n    {\n      \"location\": \"body\",\n      \"msg\": \"Invalid value\",\n      \"param\": \"email\"\n    }\n  ]\n}\n```\n\nThe default error can be customized for any validation by using `withMessage()`\n\n```dart\ncheck('name')\n  .isAlpha()\n  .withMessage('Must be alphabetical chars only')\n  .isLength({ min: 10 })\n  .withMessage('Must be at least 10 chars long')\n```\n\nNeed to have a custom validator?\n\nDon't worry, you can create your own custom function and reject the validation by throwing  Exception inside the function.\n\n```dart\nfinal validation = [\n  check('name').isLength({ min: 3 }),\n  check('email').custom((email) {\n    if (isDuplicateEmail(email)) {\n      throw Exception('Email already registered');\n    }\n  }),\n  check('age').isNumeric(),\n];\n\napp.post('/user', validation, (Req req, Res res) async {\n\n});\n```\n\n## Input Sanitization\n\nOne sensible rule to follow when you run a public server is... never trust any input from any user.\n\nEven if you have sanitized and make sure people can't enter weird things in the client side, you would still be open to people with tools (such as Chrome devtools or Postman) to send a POST request directly to your server.\n\nOr some bots trying some clever ways to find a hole in your ship.\n\nThe thing we should do is sanitize the inputs.\n\nYou may use the same `check` middleware to perform this sanitization.\n\nSay you already have a POST endpoint with complete validation.\n\n```dart\nfinal validations = [\n  check('name').isLength({ min: 3}),\n  check('email').isEmail(),\n  check('age').isNumeric(),\n];\n\napp.post('/user', validations, (Req req, Res res) async {\n  final name = req.data('name');\n  final email = req.data('email');\n  final age = req.data('age');\n\n  ...\n\n});\n```\n\nNow you may piping some sanitization methods after the validation.\n\n```dart\nfinal validations = [\n  check('name').isLength({ min: 3}),\n  check('email').isEmail().normalizeEmail(),\n  check('age').isNumeric().trim().escape(),\n];\n\napp.post('/user', validations, (Req req, Res res) async {\n  final name = req.data('name');\n  final email = req.data('email');\n  final age = req.data('age');\n\n  ...\n\n});\n```\n\nIn the above example, we use three methods:\n\n- `trim()`: to trim characters (whitespace by default) at the beginning and the end of a string\n- `escape()`: to replace `\u003c`, `\u003e`, `\u0026`, `'`, `\"` and `/` with their corresponding HTML entities\n- `normalizeEmail()`: to normalize an email address. It accepts several options to lowercase email addresses or subaddresses (e.g. lucifer+morningstar@heaven.com)\n\nThere are many other methods we can use.\n\n- `blacklist()`: remove characters that appear in the blacklist\n- `whitelist()`: remove characters that do not appear in the whitelist \n- `unescape()`: replaces HTML encoded entities\n- `ltrim`: like `trim()`, but only at the start of the string\n- `rtrim()`: like `trim()`, but only at the end of the string\n- `stripLow()`: remove ASCII control characters, which are normally invisible\n\nAnd to force conversion to some formats.\n\n- `toBool()`: convert the input string to a boolean. Everything except for '0', 'false' and '' will return true.\n- `toDate()`: convert the input string to a date, or null if the input is not a date\n- `toDouble()`: convert the input string to a double, or null if the input is not a double\n- `toInt()`: convert the input string to an integer, or null if the input is not an integer\n\nLike validators, you may also create a custom sanitizer.\n\n```dart\nfinal custom = (value) {\n  // sanitize this value however you like\n};\n  \nfinal validation = [\n  check('value').customSanitizer((value) =\u003e custom(value))\n];\n\napp.post('/user', validation, (Req req, Res res) async {\n  // get the sanitized value\n  final value = req.data('value');\n});\n```\n--\u003e\n\n## File Uploads\n\nLets say you have an HTML form that allows user to upload file.\n\n```html\n\u003cform method=\"POST\" action=\"/upload\"\u003e\n  \u003cinput type=\"file\" name=\"document\" /\u003e\n  \u003cinput type=\"submit\" value=\"Upload\" /\u003e\n\u003c/form\u003e\n```\n\nWhen press the submit button, browser will automatically send a POST request to the route `/upload`, and sending file from the input file.\n\nIt won't be sent as `application/x-www-form-urlencoded` like the standard form, but as `multipart/form-data`.\n\nHandling multipart data can be tricky and error prone, so you may use the built-in `FormParser` object that you can access with `app.form()`\n\n```dart\nfinal app = App();\nfinal form = app.form();\n\napp.post('/upload', (Req req, Res res) async {\n  await form.parse(req, (error, fields, files) {\n    if (error) {\n      print('$error');\n    }\n\n    print(fields);\n    print(files);\n  });\n});\n```\n\nYou may use it per event that will be notified whenever each file is processed. This will also notify other events, such as when processing end, when receiving other non-file field, or when an error happened.\n\n```dart\nfinal app = App();\nfinal form = app.form();\n\napp.post('/upload', (Req req, Res res) async {\n  await form\n    .onField((name, field) {\n      print('${name} ${field}');\n    })\n    .onFile((name, file) {\n      print('${name} ${file.filename}');\n    })\n    .onError((error) {\n      print('$error');\n    })\n    .onEnd(() {\n      res.end();\n    })\n    .parse(req);\n});\n```\n\nYou may also use it like this:\n\n```dart\nfinal app = App();\nfinal form = app.form();\n\napp.post('/upload', (Req req, Res res) async {\n  await form\n    .on('field', (name, field) {\n      print('${name} ${field}');\n    })\n    .on('file', (name, file) {\n      print('${name} ${file}');\n    })\n    .on('error', (error) {\n      print('$error');\n    })\n    .on('end', () {\n      res.end();\n    })\n    .parse(req);\n});\n```\n\nEither way, you will get one or more `UploadedFile` objects, containing information about the uploaded files. \n\nHere are the value you may use.\n\n- `file.name`: to get the name from input file\n- `file.filename`: to get the filename \n- `file.type`: to get the MIME type of the file\n- `file.data`: to get raw byte data of the uploaded file\n\nBy default, `FormParser` will only contains raw bytes data of the file and not save it into any folder.\n\nYou may handle it yourself like so:\n\n```dart\nimport 'package:path/path.dart' as path;\n\n// file is an UploadedFile object you get before\n\n// save to uploads directory\nString uploads = path.absolute('uploads');\n\n// use the same filename as sent by the client,\n// but feel free to use other file naming strategy\nFile f = File('$uploads/${file.filename}');\n\n// check if the file exists at uploads directory\nbool exists = await f.exists();\n\n// create file if not exists\nif (!exists) {\n  await f.create(recursive: true);\n}\n\n// write bytes data into the file\nawait f.writeAsBytes(file.data);\n\nprint('File is saved at ${f.path}');\n```\n\n## Templating\n\nLucifer provides a default templating by utilizing the `Mustache` engine. \n\nIt uses a [`mustache_template`](https://pub.dev/packages/mustache_template) package which is implemented from the [official mustache spec](https://mustache.github.io/).\n\nAs usual, to keep the core framework stays lightweight, lucifer doesn't attach any template engine to your default application. \n\nTo use the `mustache` templating engine, you may apply it first like so:\n\n```dart\nfinal app = App();\n\napp.use(mustache());\n```\n\nThen you may use the `mustache` to render any template you have in the project `views` directory.\n\nLet say you have `index.html` in the `views` directory.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch2\u003e{{ title }}\u003c/h2\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nYou may render the template using `res.render()` or `res.view()`:\n\n```dart\nfinal app = App();\n\napp.use(mustache());\n\napp.get('/', (Req req, Res res) async {\n  await res.render('index', { 'title': 'Hello Detective' });\n});\n```\n\nNow, you may run `l run` command, open http://localhost:3000, and you will see a rendered html page displaying `Hello Detective`.\n\nYou may change the default `views` directory with any directory you want.\n\n```dart\nfinal app = App();\n\n// use 'template' as the views directory\napp.use(mustache('template'));\n\napp.get('/', (Req req, Res res) async {\n  await res.view('index', { 'title': 'Hello Detective' });\n});\n```\n\nYou may add `index.html` to the `template` directory.\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch2\u003e{{ title }} from template\u003c/h2\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThen you may run it, open in the browser, and you will see another html page containing `Hello Detective from template`.\n\nFor more details on how to use `Mustache` engine, you may refer to the [mustache manual](https://mustache.github.io/mustache.5.html).\n\nIf you want to use other templating engines, such as [jinja](https://pub.dev/packages/jinja) or [jaded](https://pub.dev/packages/jaded), you may do it by handling the template rendering yourself and send the rendered html using `res.send()`\n\n```dart\napp.get('/', (Req req, Res res) async {\n  // render your jinja/jaded template into 'html' variable\n  // then send it to the client\n  await res.send(html);\n});\n```\n\nYou may also doing it by creating a custom middleware to handle templating with your chosen engine.\n\nYou may learn from the example code of the `mustache` middleware to create your own custom templating.\n\n```dart\n// \n// name it with anything you want\n// \nCallback customTemplating([String? views]) {\n  return (Req req, Res res) {\n    // \n    // you need to overwrite res.renderer\n    // using the chosen template engine\n    //\n    res.renderer = (String view, Map\u003cString, dynamic\u003e data) async {\n      // \n      // most of the time, these 2 lines will stay\n      // \n      String directory = views ?? 'views';\n      File file = File('$directory/$view.html');\n\n      // \n      // file checking also stay\n      // \n      if (await file.exists()) {\n        // \n        // mostly, all you need to do is edit the following two lines \n        // \n        Template template = Template(await file.readAsString());\n        String html = template.renderString(data);\n\n        // \n        // for the final act, send the rendered html to the client\n        //\n        await res.send(html);\n      }\n    };\n  };\n}\n```\n\nTo apply the new templating middleware, you may use `app.use()`\n\n```dart\nfinal app = App();\n\napp.use(customTemplating());\n```\n\n## Security\n\nLucifer has a built-in `security` middleware that covers a complete standard security protections for guarding your application. \n\nTo use them, you may simply apply it using `app.use()`\n\n```dart\nfinal app = App();\n\napp.use(security());\n```\n\n[Read here](https://infosec.mozilla.org/guidelines/web_security.html) to learn more about the intricacies of web security.\n\n## Error Handling\n\nLucifer will automatically handle the errors that occured in your application. \n\nHowever, you may set your own error handling using `app.on()`\n\n```dart\nfinal app = App();\n\napp.on(404, (req, res) {\n  // handle 404 Not Found Error in here\n  // such as, showing a custom 404 page\n});\n\n// another way is using StatusCode\napp.on(StatusCode.NOT_FOUND, (req, res) { });\napp.on(StatusCode.INTERNAL_SERVER_ERROR, (req, res) { });\napp.on(StatusCode.BAD_REQUEST, (req, res) { });\napp.on(StatusCode.UNAUTHORIZED, (req, res) { });\napp.on(StatusCode.PAYMENT_REQUIRED, (req, res) { });\napp.on(StatusCode.FORBIDDEN, (req, res) { });\napp.on(StatusCode.METHOD_NOT_ALLOWED, (req, res) { });\napp.on(StatusCode.REQUEST_TIMEOUT, (req, res) { });\napp.on(StatusCode.CONFLICT, (req, res) { });\napp.on(StatusCode.UNPROCESSABLE_ENTITY, (req, res) { });\napp.on(StatusCode.NOT_IMPLEMENTED, (req, res) { });\napp.on(StatusCode.SERVICE_UNAVAILABLE, (req, res) { });\n```\n\nYou may also trigger HTTP exceptions in the middleware or callback function.\n\n```dart\napp.get('/unauthorized', (Req req, Res res) async {\n  throw UnauthorizedException();\n});\n```\n\nHere is a complete list of all the HTTP exceptions that you can use in your application.\n\n```dart\nBadRequestException\n\nUnauthorizedException\n\nPaymentRequiredException\n\nForbiddenException\n\nNotFoundException\n\nMethodNotAllowedException\n\nRequestTimeoutException\n\nConflictException\n\nUnprocessableException\n\nInternalErrorException\n\nNotImplementedException\n\nServiceUnavailableException\n```\n\n## Parallel Processing\n\nParallel and multithread-ing is supported by default in Lucifer/Dart. \n\nYou may do it by distributing the application processes evenly in various isolates.\n\n```dart\nimport 'dart:async';\nimport 'dart:isolate';\n\nimport 'package:lucifer/lucifer.dart';\n\nvoid main() async {\n  // Start an app\n  await startApp();\n\n  // Spawn 10 new app with each own isolate\n  for (int i = 0; i \u003c 10; i++) {\n    Isolate.spawn(spawnApp, null);\n  }\n}\n\nvoid spawnApp(data) async {\n  await startApp();\n}\n\nFuture\u003cApp\u003e startApp() async {\n  final app = App();\n  final port = env('PORT') ?? 3000;\n\n  app.get('/', (Req req, Res res) async {\n    await res.send('Hello Detective');\n  });\n\n  await app.listen(port);\n  print('Server running at http://${app.host}:${app.port}');\n\n  return app;\n}\n```\n\n## Web Socket\n\nWeb socket is a necessary part of web application to initiate persistent communications between client and server. \n\nYou may utilize web socket in your Lucifer application like so:\n\n```dart\nimport 'dart:io';\n\nimport 'package:lucifer/lucifer.dart';\n\nvoid main() async {\n  final app = App();\n  final port = env('PORT') ?? 3000;\n\n  app.use(static('public'));\n\n  app.get('/', (Req req, Res res) async {\n    await res.sendFile('chat.html');\n  });\n\n  app.get('/ws', (Req req, Res res) async {\n    List clients = [];\n\n    final socket = app.socket(req, res);\n\n    socket.on('open', (WebSocket client) {\n      clients.add(client);\n      for (var c in clients) {\n        if (c != client) {\n          c.send('New human has joined the chat');\n        }\n      }\n    });\n    socket.on('close', (WebSocket client) {\n      clients.remove(client);\n      for (var c in clients) {\n        c.send('A human just left the chat');\n      }\n    });\n    socket.on('message', (WebSocket client, message) {\n      for (var c in clients) {\n        if (c != client) {\n          c.send(message);\n        }\n      }\n    });\n    socket.on('error', (WebSocket client, error) {\n      res.log('$error');\n    });\n    \n    await socket.listen();\n  });\n\n  await app.listen(port);\n  print('Server running at http://${app.host}:${app.port}');\n}\n```\n\n\u003c!---\n\n## Deployment\n\nAs there are many roads to Rome, there are many ways to deploy a lucifer application. \n\nYou can upload the dart code to your own server, or to a VPS, or run `dart build` and upload the generated binary file. \n\nOr use cloud service like Heroku, and use its automatic build system to help the deployment.\n\nCreate a new Heroku app.\n\n```bash\n$ heroku create \n\nCreating secure-spire-84236... done, stack is cedar-14\nhttp://secure-spire-84236.herokuapp.com/ ...\nGit remote heroku added\n```\n\nThis will generate a random name for your app, in this case `secure-spire-84236`\n\nCommit the project and add heroku remote.\n\n```bash\n$ git init \n$ git add .\n$ git commit -m \"Initial commit\"\n\n$ heroku git:remote -a secure-spire-84236\n```\n\nConfigure the buildpack.\n\nRunning the deploy command now (`git push heroku master`) will throw an exception since Heroku needs the correct buildpack for running Dart apps.\n\nA buildpack contains scripts that set the necessary dependencies to build and serve a project. \n\nHeroku does not officially support Dart at present time. \n\n```bash\n$ heroku buildpacks:set https://github.com/igrigorik/heroku-buildpack-dart.git\n```\n\nSet the required configurations needed by the buildpack:\n\n```bash\n$ heroku config:set DART_SDK_URL=https://storage.googleapis.com/dart-archive/channels/dev/release/2.0.0-dev.67.0/sdk/dartsdk-linux-x64-release.zip\n\n$ heroku config:set DART_BUILD_CMD=\"/app/dart-sdk/bin/pub global activate webdev \u0026\u0026 /app/dart-sdk/bin/pub global run webdev build\"\n```\n\nThis tells the buildpack to pull the latest Dart SDK and build the project using the webdev tool.\n\nCreate a `Procfile` at the project root directory with instructions to start our server.\n\n```text\nweb: ./dart-sdk/bin/dart bin/main.dart\n```\n\nCommit the changes and start deploying.\n\n```bash\n$ git push heroku master\n```\n--\u003e\n\n\u003c!---\n## REST API\n\nThis part will includes a simple tutorial to create a fully functioning REST API with Lucifer.\n--\u003e\n\n\u003c!---\n## GraphQL\n\nThis part will includes a simple tutorial to create a fully functioning GraphQL with Lucifer.\n--\u003e\n\n## Contributions\n\nFeel free to contribute to the project in any ways. \n\nThis includes code reviews, pull requests, documentations, tutorials, or reporting bugs that you might found in Lucifer.\n\n## License \n\nMIT License\n\nCopyright (c) 2021 Lucifer\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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\nAUTHORS 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\nSOFTWARE.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalkuadrat%2Flucifer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalkuadrat%2Flucifer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalkuadrat%2Flucifer/lists"}