README.md

# 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.