# Mflask
Mflask is a small, Flask-inspired web framework for Elixir built on top of Plug and Bandit.
It provides a minimal, expressive routing DSL, simple response helpers, and a few convenience
middleware modules so you can write tiny web apps with familiar patterns.
This README covers:
- Quick start
- Routing and response helpers
- Middleware
- Templates
- Running example apps with the `mix mf.serve` task
- Testing
- Contributing and license
---
## Quick start
Add `mflask` to your dependencies (if using this project as a dependency). For local development,
you can use the example app pattern or run a module directly with the included mix task.
Example minimal app:
1. Create a file `example_app.ex`:
```elixir
defmodule ExampleApp do
use Mflask
get "/" do
text(conn, "Hello, world!")
end
get "/hello/:name" do
name = path_param(conn, "name") || "friend"
html(conn, "<h1>Hello #{name}</h1>")
end
end
```
2. Serve the app from the directory containing `example_app.ex`:
```bash
# from the project root (or directory containing example_app.ex)
mix mf.serve . --port 4000
```
Open `http://localhost:4000` to see the app.
---
## Router and DSL
Mflask exposes a compact routing DSL inspired by Flask. Inside a module `use Mflask` to get
routing macros and helpers.
Supported route macros:
- `get "/path" do ... end`
- `post "/path" do ... end`
- `put "/path" do ... end`
- `patch "/path" do ... end`
- `delete "/path" do ... end`
- `options "/path" do ... end`
- `head "/path" do ... end`
Path parameters:
- Use `:name` in the path to capture segments.
- Retrieve with `path_param(conn, "name")` or access the internal assigns via `conn.assigns[:mflask_params]`.
Query and body helpers:
- `query_param(conn, "q", default)` — returns the first value if multiple are present.
- `body_param(conn, "k", default)` and `body_params(conn)` — require body-parsing middleware (see below).
Example:
```elixir
defmodule UsersApp do
use Mflask
get "/users/:id" do
id = path_param(conn, "id")
json(conn, %{user_id: id})
end
get "/search" do
q = query_param(conn, "q", "")
json(conn, %{query: q})
end
end
```
---
## Response helpers
Mflask provides helpers to send common response types:
- `text(conn, "hello", status \\ 200)` — send plain text.
- `html(conn, "<h1>Hi</h1>", status \\ 200)` — send HTML.
- `json(conn, data, status \\ 200)` — send JSON (uses `Jason`).
- `redirect(conn, "/path", status \\ 302)` — perform redirects.
- `send_file(conn, path, status \\ 200)` — send files with MIME detection.
These are imported automatically into router modules.
---
## Middleware
Mflask supports composing plugs as middleware. Use `Mflask.Router.plug/2` in a router to attach
middleware.
Included middleware:
- `Mflask.Middleware.BodyParser` — JSON, urlencoded and multipart parsing (uses `Plug.Parsers` + `Jason`).
- `Mflask.Middleware.Static` — simple static file serving with `:at` and `:from` options.
- `Mflask.Middleware.Logger` — simple request logging.
Example:
```elixir
defmodule ApiApp do
use Mflask
# attach middleware for this router
Mflask.Router.plug(Mflask.Middleware.BodyParser)
Mflask.Router.plug(Mflask.Middleware.Logger)
post "/echo" do
json(conn, body_params(conn))
end
end
```
Notes:
- Middleware are executed in the order they are declared.
- If a plug halts the connection, the router will not dispatch further routes.
- The `BodyParser` currently supports `application/json` and `application/x-www-form-urlencoded` out of the box.
---
## Templates
Mflask ships a small EEx-based helper `Mflask.Template`:
- `render(conn, template_path, assigns \\ [])` — render a template file and send as HTML.
Options include `layout:` and `status:`.
- `render_string(template_string, assigns_or_opts \\ [])` — evaluate a template string.
When rendering a template with a `layout:`, the layout can reference `<%= @inner_content %>`.
Helpers ensure a couple of common assigns are present to avoid warnings.
Example:
```heex
inner = "<p>Inner content: <%= @name %></p>"
layout = "<html><body><%= @inner_content %><footer>v<%= @ver %></footer></body></html>"
full =
Mflask.Template.render_string(inner, assigns: [name: "Alice"])
|> then(fn content ->
Mflask.Template.render_string(layout, assigns: [inner_content: content, ver: "0.1"])
end)
```
---
## Running example apps with `mix mf.serve`
The project includes a mix task `mix mf.serve` to load Elixir files from a directory and start a Bandit server.
Usage:
```bash
mix mf.serve [PATH] [--module MyApp] [--port 4000] [--ip 127.0.0.1]
# examples:
mix mf.serve examples/ # scan and pick a module to serve (prefers ExampleApp)
mix mf.serve . --module ExampleApp # explicitly serve ExampleApp from current dir
mix mf.serve examples/ --port 8080 # serve on port 8080
```
Behavior:
- Requires (`Code.require_file/1`) all `.ex` / `.exs` files under `PATH`.
- Detects modules defined in those files and selects a module to serve (prefers `ExampleApp`).
- Validates the chosen module exports `call/2` (i.e. is Plug-compatible).
- Starts Bandit and blocks until you stop it.
Security note: requiring arbitrary code will execute top-level code. Only run this task on trusted source trees.
---
## Testing
This project uses ExUnit. Tests are split into focused files under `test/`:
- Run the full test suite:
```bash
mix test
```
- Test helpers use `Plug.Test` to simulate requests for most units.
---
## Development notes
- Supported Elixir versions: specified in `mix.exs`.
- Key dependencies: `plug`, `bandit`, `jason`, `mime`.
- The router compiles route definitions into private handler functions and dispatch clauses.
- The `Mflask.Request` helpers provide small utilities to get query/path/body params and remote IP.
- The `Mflask.Response` helpers sanitize values when encoding JSON to avoid errors with internal structures.
---
## Contributing
Contributions are welcome. Suggested workflow:
1. Fork the repository.
2. Create a feature branch.
3. Add or update tests when introducing behavior changes.
4. Run `mix test` and ensure all tests pass.
5. Open a merge request describing the change.
Please follow idiomatic Elixir formatting (`mix format`) and keep commits focused.
---
## License
Mflask is distributed under the GPL-3.0 license. See `LICENSE.md` for details.
---
## Contact / Support
If you run into issues, report them through the project tracker. For quick questions, open an issue with a minimal reproduction.