Marten Web Framework

repository·main·Indexed 19 days ago

https://github.com/martenframework/marten

A 'batteries included' Crystal web framework designed for pragmatic development and rapid prototyping. Marten provides built-in tools for ORM, migrations, authentication, and asset management, featuring a DSL for database models, a handler-based request processing system, and a flexible templating engine.

Tokens
139.5K
Snippets
532
Records
657
Agent score
65%

What's inside Marten

  1. Core features and 'batteries included' capabilities of Marten

    main

    Marten follows a 'batteries included' philosophy, providing several essential web application features out of the box without requiring immediate external configuration. These built-in features include:

    • ORM: Object-Relational Mapping for database interactions.
    • Migrations: Tools for managing database schema changes.
    • Translations: Internationalization (i18n) support.
    • Templating Engine: For rendering views.
    • Sessions: Managing user sessions.
    • Emailing: Support for sending emails.
    • Authentication: Handling user identity and access.
  2. Navigate the Marten documentation

    main

    The Marten documentation is organized into several types of content to help you find information based on your needs:

    • Topic-specific guides: Explain core framework concepts such as models, handlers, and templates.
    • Reference pages: Provide a curated technical reference of the framework's public APIs.
    • How-to guides: Offer solutions for common tasks like deployment and application development.
    • API Reference: An automatically-generated API reference is available for deep dives into Marten's internals.
  3. Project structure overview

    main

    A standard Marten project generated via marten new includes the following structure:

    PathDescription
    config/Project configuration, including environment-specific settings, initializers, and routes.
    spec/Project specifications for testing.
    src/Application source code. Contains project.cr (dependencies), server.cr (server entrypoint), cli.cr (CLI abstractions), and subdirectories for assets, emails, handlers, migrations, models, schemas, and templates.
    manage.crCLI tool for project interaction (e.g., running migrations, collecting assets).
    seed.crLogic for populating the database with initial/default data.
    shard.ymlCrystal dependency list.
    .editorconfigCoding style definitions.
    .gitignoreGit ignore configuration.
  4. How handler callbacks work in Marten

    main

    Callbacks are methods triggered at specific stages of a handler's lifecycle. They allow you to intercept requests, modify responses, or manipulate data before or after standard operations like #dispatch, #render, or schema validation.

    To use a callback, you must explicitly register it in your handler class using a callback macro (e.g., before_dispatch :method_name) and provide a symbol representing the method to be executed.

    Key Behavior: Bypassing Logic If a callback returns a Marten::HTTP::Response object, that response is used immediately, and subsequent standard handler logic (like the #dispatch method or following callbacks) is bypassed.

    class MyHandler < Marten::Handler
      before_dispatch :my_callback_method
    
      private def my_callback_method
        # logic here
      end
    end
  5. Define database relationships

    main

    Marten supports three primary relationship types using special field types and the to: argument:

    1. Many-to-one: Use :many_to_one. The current model has one related record, but the target model can be associated with many of these. Use to: self for recursive relationships.
    2. One-to-one: Use :one_to_one. Similar to many-to-one, but enforces a uniqueness constraint so each record is associated with at most one other record.
    3. Many-to-many: Use :many_to_many. Both models can be associated with multiple records of the other.
    # Many-to-one
    class Article < Marten::Model
      field :author, :many_to_one, to: Author
    end
    
    # Recursive Many-to-one
    class TreeNode < Marten::Model
      field :parent, :many_to_one, to: self
    end
    
    # One-to-one
    class User < Marten::Model
      field :profile, :one_to_one, to: Profile
    end
    
    # Many-to-many
    class Article < Marten::Model
      field :tags, :many_to_many, to: Tag
    end
  6. Define model validation rules using fields

    main

    Marten models allow you to define validation rules directly within field definitions. Rules are inherited from the field type and the options provided. For example, using blank: false ensures a field is not empty, and max_size: N restricts the length of string fields. These rules are database-agnostic and run automatically before persistence.

    Common field-based validation triggers include:

    • Field Type: e.g., a uuid field validates that the value is a valid UUID.
    • Field Options: e.g., blank: false or max_size: 128.
    class User < Marten::Model
      field :id, :big_int, primary_key: true, auto: true
      field :name, :string, max_size: 128, blank: false
    end
  7. Manage Marten environments

    main

    Marten uses the MARTEN_ENV environment variable to determine the current running environment. If MARTEN_ENV is not set, the application defaults to development.

    The value of MARTEN_ENV must match the argument passed to Marten.configure.

    When using the new management command, Marten automatically creates the following environment files:

    • config/settings/development.cr
    • config/settings/test.cr
    • config/settings/production.cr
  8. Configure emailing backends

    main

    Emailing backends determine how emails are delivered (e.g., printing to stdout in development or using an SMTP server in production).

    • Global Configuration: Controlled by the emailing.backend setting.
    • Per-Email Override: You can override the backend for a specific email class using the #backend class method.
    class WelcomeEmail < Marten::Email
      # ... properties ...
      backend Marten::Emailing::Backend::Development.new(print_emails: true)
    
      def initialize(@user : User)
      end
    end
    class WelcomeEmail < Marten::Email
      from "no-reply@martenframework.com"
      to @user.email
      subject "Hello!"
      template_name "emails/welcome_email.html"
    
      backend Marten::Emailing::Backend::Development.new(print_emails: true)
    
      def initialize(@user : User)
      end
    end
  9. How file objects work in Marten

    main

    When you access a :file or :image field on a model, Marten returns an instance of Marten::DB::Field::File::File. These objects represent the association between the model and the file.

    File objects have two key states:

    • Attached: The object has an associated file set (#attached? returns true).
    • Committed: The file has been successfully persisted to the underlying storage (#committed? returns true).

    Note that file objects are always associated with a model record (whether persisted or not) and are only available via :file model fields.

    attachment = Attachment.last!
    attachment.uploaded_file.attached?  # => true
    attachment.uploaded_file.committed? # => true
  10. Enable backward relations with the `related` argument

    main

    By default, many_to_one fields do not allow you to navigate from the target record back to the referencing records. To enable this, use the related: argument in your field definition.

    When you define field :author, :many_to_one, to: Author, related: :articles, Marten automatically creates an Author#articles method. This method returns a query set, allowing you to chain filters (e.g., .filter(...)) or convert it to an array using .to_a.

    class Article < Marten::Model
      field :id, :big_int, primary_key: true, auto: true
      field :title, :string, max_size: 128
      field :author, :many_to_one, to: Author, related: :articles
    end
    
    # Usage:
    author = Author.first
    author.articles.to_a          # Returns all articles for this author
    author.articles.filter(title__startswith: "Top") # Returns filtered query set
    
    # You can also create related records through the relation:
    author.articles.create!(title: "Fourth article")
  11. Understand Cross-Site Request Forgery (CSRF) protection

    main

    Marten provides built-in CSRF protection that is automatically enabled for handlers. It works by verifying a token for all 'unsafe' HTTP requests (methods other than GET, HEAD, OPTIONS, or TRACE).

    How it works:

    1. A token is stored in client cookies.
    2. The client must provide this token in the request data or via a specific header.
    3. Marten rejects the request if the tokens are invalid or if the cookie-based token does not match the one provided in the request.

    Configuration: Control the CSRF protection mechanism using dedicated settings found in the framework settings documentation.