projectrules.ai

Phoenix Framework Best Practices

elixirphoenixbest-practicescode-organizationperformance

Description

This rule outlines the best practices and coding standards for developing Elixir applications with the Phoenix framework, covering code organization, performance, security, testing, and common pitfalls.

Globs

**/*.{ex,exs,eex,leex,sface}
---
description: This rule outlines the best practices and coding standards for developing Elixir applications with the Phoenix framework, covering code organization, performance, security, testing, and common pitfalls.
globs: **/*.{ex,exs,eex,leex,sface}
---

- # Phoenix Framework Best Practices

  This document outlines general best practices for developing Elixir applications with the Phoenix Framework. It aims to promote maintainable, scalable, and performant codebases.

- ## 1. Code Organization and Structure

  - ### 1.1. Directory Structure Best Practices

    - **`lib/`**: Contains the core application logic, including contexts, schemas, and supporting modules.
    - **`lib/<app_name>_web/`**: Holds the web-related components, such as controllers, views, templates, channels, and live views.
    - **`lib/<app_name>/`**: Contains your application’s core domain logic. Favor placing most of your code here.
    - **`test/`**: Contains all test-related files, including unit, integration, and acceptance tests.
    - **`priv/repo/migrations/`**: Stores database migration files.
    - **`config/`**: Contains configuration files for different environments (dev, test, prod).
    - **`assets/`**: Contains static assets like JavaScript, CSS, and images. Managed by tools like esbuild or Webpack.
    - **`deps/`**: Automatically generated folder containing project dependencies.
    - **`rel/`**: Used for building releases with distillery or mix release. Contains configuration for releases.

  - ### 1.2. File Naming Conventions

    - Use `snake_case` for file names (e.g., `user_controller.ex`, `user_schema.ex`).
    - Match the file name to the module name (e.g., `UserController` module should be in `user_controller.ex`).
    - For LiveView components, use `_live` suffix (e.g., `user_live.ex`).
    - For schema files, use `_schema` suffix(e.g. `user_schema.ex`).

  - ### 1.3. Module Organization

    - Organize modules into contexts, representing logical domains within the application (e.g., `Accounts`, `Blog`, `Payments`).
    - Keep modules small and focused, adhering to the Single Responsibility Principle (SRP).
    - Use namespaces to group related modules (e.g., `MyApp.Accounts.User`, `MyApp.Blog.Post`).
    - Favor explicit dependencies between modules to promote clarity and reduce coupling.

  - ### 1.4. Component Architecture (LiveView)

    - Break down complex LiveView pages into smaller, reusable components.
    - Use functional components (HEEx templates with function definitions) for simple UI elements.
    - Use LiveComponent modules for more complex, stateful components with event handling.
    - Organize components into directories based on their purpose or domain.
    - Define attributes and slots for LiveView components to ensure type safety.
    - Utilize PubSub for communication between LiveView components.

  - ### 1.5. Code Splitting Strategies

    - Separate concerns by using contexts. Controllers delegate to contexts, which handle the business logic, and contexts rely on schemas, which handle the data validations and structure.
    - Utilize umbrella projects to divide a large application into smaller, independent applications.
    - Consider code splitting at the LiveView level, using separate LiveView modules for different sections of a page.
    - Use dynamic imports for JavaScript modules to load code on demand.

- ## 2. Common Patterns and Anti-patterns

  - ### 2.1. Design Patterns

    - **MVC (Model-View-Controller)**: The core architectural pattern in Phoenix. Ensure proper separation of concerns between models (schemas), views (templates), and controllers.
    - **Context Pattern**: Encapsulate business logic and data access within contexts to provide a clear API for controllers and other parts of the application.
    - **Repository Pattern**: Abstract away database interactions behind a repository interface to allow for easier testing and potential database changes.  While contexts often serve this purpose in Phoenix, a dedicated repository layer can be useful for very complex data access logic.
    - **PubSub**: Use `Phoenix.PubSub` for real-time communication between different parts of the application (e.g., LiveView components, channels).
    - **Ecto Changesets**: Employ Ecto changesets for data validation and sanitization before persisting to the database.

  - ### 2.2. Recommended Approaches for Common Tasks

    - **Authentication**: Use a library like `Pow` or `Ueberauth` for handling user authentication and authorization.
    - **Authorization**: Implement role-based access control (RBAC) or attribute-based access control (ABAC) using libraries like `Pomegranate` or custom logic.
    - **Form Handling**: Use `Phoenix.HTML.Form` helpers and Ecto changesets for building and validating forms.
    - **Real-time Updates**: Leverage Phoenix Channels or LiveView for real-time updates and interactive features.
    - **Background Jobs**: Use `Oban` for reliable background job processing.

  - ### 2.3. Anti-patterns and Code Smells

    - **Fat Controllers**: Avoid putting too much logic in controllers. Delegate to contexts or services.
    - **Direct Repo Access in Controllers**: Do not directly call `Repo` functions in controllers. Use contexts.
    - **Ignoring Changeset Errors**: Always handle changeset errors and display appropriate messages to the user.
    - **Over-Complicated Queries**: Keep Ecto queries simple and composable. Use views or functions in schema modules to build more complex queries.
    - **Unnecessary Global State**: Minimize the use of global state. Prefer passing data explicitly between functions and components.

  - ### 2.4. State Management Best Practices (LiveView)

    - Store only the minimal required state in LiveView's `assigns`.
    - Use `handle_params` to load data based on URL parameters.
    - Employ `handle_info` for handling asynchronous events and updates.
    - Implement proper state cleanup in `mount` and `terminate` callbacks.
    - Consider using a global state management library like `global` only when truly necessary for cross-component communication.

  - ### 2.5. Error Handling Patterns

    - Use pattern matching to handle different error scenarios.
    - Raise exceptions only for truly exceptional cases.  For expected errors, return `{:error, reason}` tuples.
    - Implement error logging and monitoring using tools like `Sentry` or `Bugsnag`.
    - Use `try...rescue` blocks for handling potential exceptions during external API calls or file operations.
    - Define custom error types using atoms or structs to provide more context for error handling.

- ## 3. Performance Considerations

  - ### 3.1. Optimization Techniques

    - **Database Queries**: Optimize Ecto queries by using indexes, preloading associations, and avoiding N+1 queries.
    - **Caching**: Implement caching for frequently accessed data using `ETS` tables or a dedicated caching library like `Cachex`.
    - **Concurrency**: Leverage Elixir's concurrency features (e.g., `Task`, `GenServer`) to handle concurrent requests and background tasks efficiently.
    - **Code Profiling**: Use tools like `Erlang's observer` or `fprof` to identify performance bottlenecks in your code.
    - **Connection Pooling**: Ensure proper database connection pooling to minimize connection overhead.

  - ### 3.2. Memory Management

    - **Avoid Memory Leaks**: Be mindful of memory leaks, especially in long-running processes like `GenServers`.
    - **Use Streams**: Employ Elixir streams for processing large datasets in a memory-efficient manner.
    - **Garbage Collection**: Understand Erlang's garbage collection behavior and optimize code to minimize garbage collection overhead.

  - ### 3.3. Rendering Optimization (LiveView)

    - **Minimize DOM Updates**: Reduce the number of DOM updates in LiveView by using `phx-update` attributes and smart diffing.
    - **Use Static Content**: Serve static content (e.g., images, CSS, JavaScript) directly from the web server without involving LiveView.
    - **Optimize Templates**: Optimize HEEx templates for efficient rendering.

  - ### 3.4. Bundle Size Optimization

    - **Code Splitting**: Use code splitting to reduce the initial bundle size and load code on demand.
    - **Tree Shaking**: Enable tree shaking in esbuild or Webpack to remove unused code from the bundle.
    - **Minification**: Minify CSS and JavaScript files to reduce their size.
    - **Image Optimization**: Optimize images for web use by compressing them and using appropriate formats.

  - ### 3.5. Lazy Loading

    - **Lazy Load Images**: Implement lazy loading for images to improve initial page load time.
    - **Lazy Load Components**: Use dynamic imports or conditional rendering to lazy load LiveView components.

- ## 4. Security Best Practices

  - ### 4.1. Common Vulnerabilities and Prevention

    - **Cross-Site Scripting (XSS)**: Sanitize user input and use proper escaping in templates to prevent XSS attacks. Phoenix's HEEx templates automatically escape variables, but be careful when using `raw/1` or `safe/1`.
    - **Cross-Site Request Forgery (CSRF)**: Protect against CSRF attacks by using Phoenix's built-in CSRF protection mechanisms. Include the CSRF token in forms and AJAX requests.
    - **SQL Injection**: Use Ecto's parameterized queries to prevent SQL injection vulnerabilities.
    - **Authentication and Authorization Issues**: Implement robust authentication and authorization mechanisms to protect sensitive data and functionality.

  - ### 4.2. Input Validation

    - **Use Ecto Changesets**: Always validate user input using Ecto changesets to ensure data integrity and prevent malicious input.
    - **Whitelist Input**: Define a whitelist of allowed input values instead of a blacklist of disallowed values.
    - **Sanitize Input**: Sanitize user input to remove potentially harmful characters or code.

  - ### 4.3. Authentication and Authorization

    - **Use Strong Passwords**: Enforce strong password policies and use proper hashing algorithms (e.g., `bcrypt`) to store passwords securely.
    - **Implement Multi-Factor Authentication (MFA)**: Add an extra layer of security by requiring users to authenticate using multiple factors.
    - **Use JWT (JSON Web Tokens)**: Use JWT for secure API authentication and authorization.
    - **Implement Role-Based Access Control (RBAC)**: Define roles and permissions to control access to different parts of the application.

  - ### 4.4. Data Protection

    - **Encrypt Sensitive Data**: Encrypt sensitive data at rest and in transit using appropriate encryption algorithms.
    - **Use HTTPS**: Always use HTTPS to encrypt communication between the client and the server.
    - **Protect API Keys**: Store API keys securely and restrict access to them.

  - ### 4.5. Secure API Communication

    - **Use HTTPS**: Enforce HTTPS for all API communication.
    - **Validate API Requests**: Validate API requests to prevent malicious input and unauthorized access.
    - **Rate Limiting**: Implement rate limiting to prevent abuse and denial-of-service attacks.
    - **API Versioning**: Use API versioning to ensure backward compatibility and allow for future API changes.

- ## 5. Testing Approaches

  - ### 5.1. Unit Testing

    - **Test Contexts and Schemas**: Write unit tests for contexts and schemas to ensure that business logic and data validation work as expected.
    - **Mock External Dependencies**: Use mocking libraries like `Mock` or `Meck` to isolate units under test and avoid dependencies on external services.
    - **Test Edge Cases**: Test edge cases and boundary conditions to ensure that code handles unexpected input correctly.

  - ### 5.2. Integration Testing

    - **Test Controllers and Channels**: Write integration tests for controllers and channels to ensure that they interact correctly with contexts and other parts of the application.
    - **Use a Test Database**: Use a dedicated test database to avoid affecting the production database.
    - **Verify Database Interactions**: Verify that database interactions are performed correctly by inspecting the database state after running tests.

  - ### 5.3. End-to-End Testing

    - **Use Wallaby or Cypress**: Use end-to-end testing frameworks like Wallaby or Cypress to simulate user interactions and verify that the application works as expected from the user's perspective.
    - **Test Critical User Flows**: Test critical user flows to ensure that the most important features of the application are working correctly.

  - ### 5.4. Test Organization

    - **Organize Tests by Module**: Organize tests into directories that mirror the application's module structure.
    - **Use Descriptive Test Names**: Use descriptive test names to clearly indicate what each test is verifying.
    - **Keep Tests Small and Focused**: Keep tests small and focused to make them easier to understand and maintain.

  - ### 5.5. Mocking and Stubbing

    - **Use Mock Libraries**: Use mocking libraries like `Mock` or `Meck` to create mock objects and stub function calls.
    - **Avoid Over-Mocking**: Avoid over-mocking, as it can make tests less realistic and harder to maintain.
    - **Use Stubs for External Dependencies**: Use stubs to replace external dependencies with simplified versions that are easier to control during testing.

- ## 6. Common Pitfalls and Gotchas

  - ### 6.1. Frequent Mistakes

    - **Not Using Contexts**: Directly accessing the Repo in controllers or other modules, bypassing the business logic encapsulation provided by contexts.
    - **Ignoring Changeset Validation Errors**: Neglecting to handle and display changeset validation errors to the user, leading to confusing behavior.
    - **N+1 Queries**: Failing to preload associations in Ecto queries, resulting in performance bottlenecks.
    - **Over-reliance on Global State**: Using global state when it's not necessary, making code harder to reason about and test.

  - ### 6.2. Edge Cases

    - **Handling Timezones**: Dealing with timezones correctly, especially when storing and displaying dates and times to users in different locations.
    - **Concurrency Issues**: Avoiding race conditions and other concurrency issues when using Elixir's concurrency features.
    - **Large File Uploads**: Handling large file uploads efficiently and securely.

  - ### 6.3. Version-Specific Issues

    - **LiveView Updates**: Staying up-to-date with LiveView updates and understanding potential breaking changes.
    - **Ecto Version Compatibility**: Ensuring compatibility between Ecto and other dependencies.

  - ### 6.4. Compatibility Concerns

    - **JavaScript Framework Integration**: Integrating Phoenix with JavaScript frameworks like React or Vue.js can introduce compatibility issues.
    - **Database Driver Compatibility**: Ensuring compatibility between Ecto and the chosen database driver.

  - ### 6.5. Debugging Strategies

    - **Use IEx.pry**: Use `IEx.pry` to pause execution and inspect variables.
    - **Enable Debug Logging**: Enable debug logging to get more information about what's happening in the application.
    - **Use Remote Debugging**: Use remote debugging tools to debug applications running in production environments.
    - **Analyze Logs**: Analyze logs to identify errors and performance issues.

- ## 7. Tooling and Environment

  - ### 7.1. Recommended Development Tools

    - **Visual Studio Code**: VS Code with the ElixirLS extension provides excellent Elixir support, including code completion, linting, and debugging.
    - **IntelliJ IDEA**: IntelliJ IDEA with the Elixir plugin also provides excellent Elixir support.
    - **Mix**: Elixir's build tool, used for creating, compiling, testing, and managing dependencies.
    - **IEx**: Elixir's interactive shell, used for exploring code and debugging.
    - **Erlang Observer**: Erlang's GUI tool for monitoring and debugging Erlang/Elixir applications.

  - ### 7.2. Build Configuration

    - **Use `mix.exs`**: Use `mix.exs` to manage project dependencies, compilation settings, and other build configurations.
    - **Define Environments**: Define separate environments for development, testing, and production.
    - **Use Configuration Files**: Use configuration files (`config/config.exs`, `config/dev.exs`, `config/test.exs`, `config/prod.exs`) to configure the application for different environments.
    - **Secrets Management**: Use environment variables or dedicated secrets management tools to store sensitive information like API keys and database passwords.

  - ### 7.3. Linting and Formatting

    - **Use `mix format`**: Use `mix format` to automatically format Elixir code according to a consistent style.
    - **Use Credo**: Use Credo to analyze Elixir code for style and potential issues.
    - **Configure Editor**: Configure the editor to automatically format code on save.

  - ### 7.4. Deployment

    - **Use Mix Release**: Use `mix release` to build and deploy Elixir applications. Distillery is deprecated.
    - **Use Docker**: Use Docker to containerize Elixir applications for easy deployment and scaling.
    - **Deploy to Cloud Platforms**: Deploy Elixir applications to cloud platforms like Heroku, AWS, Google Cloud, or Azure.
    - **Use a Process Manager**: Use a process manager like `systemd` or `pm2` to manage Elixir application processes.

  - ### 7.5. CI/CD Integration

    - **Use GitHub Actions, GitLab CI, or CircleCI**: Use CI/CD platforms to automate the build, test, and deployment processes.
    - **Run Tests on CI**: Run tests on CI to ensure that code changes don't introduce regressions.
    - **Automate Deployments**: Automate deployments to production environments after successful tests.

- ## Additional Resources

  - [Phoenix Framework Documentation](https://www.phoenixframework.org/docs)
  - [Elixir Language Documentation](https://elixir-lang.org/docs.html)
  - [Ecto Documentation](https://hexdocs.pm/ecto/Ecto.html)
  - [Phoenix LiveView Documentation](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html)
  - [Oban Documentation](https://github.com/sorentwo/oban)
  - [Credo Documentation](https://hexdocs.pm/credo/Credo.html)
Phoenix Framework Best Practices