This article compares the Frappe and the Laravel frameworks, focusing on key technical differences in customization, database management, and API development. While Laravel’s popularity lies in its code-centric approach to web development, Frappe simplifies many aspects through configuration and low-code features, making it a suitable choice for rapid application development. This article will be broken into three parts which will explore “Configuration vs. code customization,” “database migrations,” and “API integration techniques,” providing practical insights on why to choose each framework.
What is Frappe?
My previous articles discussed Frappe, so there will just be a briefing on its introduction. Frappe is basically written in Python and JavaScript and is a battery-oriented full-stack web framework that uses MariaDB as its database. It is called battery-oriented because it gives programmers a full range of tools to quickly create web apps. It is primarily used with ERP Next.
Key features of Frappe
- Frappe’s modular architecture allows developers to break up more complex programs into smaller, reusable components. This increases scalability, facilitates maintenance, and promotes code reuse.
- Database work is made more accessible by the Frappe ORM layer, which encapsulates database activities and drops the need for explicit SQL query development. This simplifies database administration and reduces development time.
- Frappe’s large Restful API enables developers to build dependable APIs for their applications. This API is easy to integrate with frontend frameworks and external services because it follows REST standards.
- Frappe Desk is an integrated development environment(IDE) that offers a comprehensive toolkit for managing application data, establishing workflows, and altering user interface elements. Productivity is increased, and application development is streamlined with this IDE.
- Frappe has built-in form builders.
What is Laravel?
Laravel is an open-source, free PHP programming language framework that makes development practices appealing without tampering with the quality. It uses a Model View Controller pattern, making the language more relatable and adaptable. It is equally a web application framework that has an expressive and elegant syntax trying to ease the pain of development by simplifying common tasks used in web applications and projects.
Key features of Laravel
- It is a fantastic fit for Restful APIs since it manages routes very straightforwardly and can provide a single interface between your data and client. It basically permits queries to a certain URL, e.g., ‘login’ is helpful for websites and social networks.
- The Auth session facades in Laravel provide access to the built-in authentication and session services. It provides cookie-based authentication, mostly for browser-based queries.
- Laravel has an eloquent ORM (Object Relational Mapping) package, making it easier to interact with the database without using SQL Queries.
- Users can easily retrieve stored items in Laravel because of the session and cache handling feature. Requests are collected, and information is stored in drives like Redis, File, and Memcached.
- The blade template engine is included in the Laravel framework. This engine produced
.blade.php
files with a few blade directives and HTML templates.
Configuration vs Code-Centric Customisation
This session will focus on customization in the Frappe framework and Laravel. Frappe customization is done through configuration, while in Laravel, it is code centric.
Configuration-Driven approach in Frappe.
Most of the time, customization is done through configuration rather than extensive coding in the Frappe framework. This configuration-heavy approach offers unique advantages, especially for teams looking to build and deploy applications quickly without much use of code. There’s a breakdown of how this approach works alongside the examples and benefits.
Frappe’s framework is structured to let the users manage the application’s core functionality using GUI (Graphical User Interfaces) and configuration files rather than requiring complex codes. Frappe’s DocTypes, Forms, and Workflow components can be fully configured through point-and-click actions or by editing JSON files.
Advantages of Configuration for Rapid Customization
- Since Frappe emphasizes configuration over coding, developers can quickly define models, forms, and workflows, which significantly speeds up the development cycle.
- The GUI approach minimizes the likelihood of syntax and logic errors, as configuration is standardized and validated by the platform, leading to fewer bugs.
- Changes to forms, fields or workflows can be made in real-time through the GUI, allowing for more flexibility in adapting to changing requirements without code rewrites.
- Built-in settings allow fine-grained access control by configuring permissions directly in doctypes and workflows. This simplifies security configurations while reducing the need for custom custom access logic.
Creating forms and views using Frappe’s built-in tools.
I showed in my earlier article, Frappe installation and here are the steps in creating forms and views using Frappe’s built-in tools.
In this example, we’ll create Frappe’s forms and views using the built-in tools without coding. These examples will demonstrate how to use Frappe’s user interface to create and customize doctypes, which are the foundations for forms and views in Frappe.
Create a “Customer Feedback” doctype.
Let’s start by creating a doctype for customer feedback.
- Go to the Desk in your Frappe installation.
- Navigate to “Developer” > “Doctype” > “New”
- Fill in the basic details.
- Name: Customer Feedback
- Module: CRM(or create a new module if needed)
- Is submittable: Check this box
- In the “Fields” table, add the following fields.
- Customer Name (Data, required)
- Feedback Date (Date, required)
- Rating (Rating, required)
- Feedback (Text editor)
- Follow up required (Check)
- In the “Form Settings” section.
- Enable “Quick Entry”.
- Set “Title Field” to “Customer Name”
- Save the doctype.
Now let’s improve the form layout for better user experience.
- In the doctype view, click on “Customize Form”.
- Rearrange fields and add sections.
- Add the “Section Break” after “Customer’s Name”
- Add a “Column Break” between “Feedback Date” and “Rating”
- Add another “Section Break” before “Feedback”
- Customize field properties:
- Set “Feedback” field to full width
- Make “Follow Up Required” bold for emphasis
- Save your customizations.
Let’s configure how the Customer Feedback entries appear in the List View
- In the doctype, scroll to the “List View Settings” section.
- Set “List View Fields” to:
- Customer Name
- Feedback Date
- Rating
- Follow Up Required
- In the “Sort Field”, choose “Feedback Date”
- In “Sort Order”, select “DESC” for most recent first
- Save the doctype.
These images demonstrate how the Customer Feedback Doctype we created using Frappe’s built-in tools might look when implemented as web components. The actual Frappe would be similar in functionality but may differ in appearance.
Note that in Frappe, you create these views primarily through the user interface without needing to write code.
Code-Centric approach in Laravel
In this section, we’ll discuss Laravel’s code-centric approach, focusing on how it relies on controllers, models, views, and custom routes for customization. I’ll also explain how this section provides granular control for developers.
Laravel is known for its elegant syntax and code-centric approach to web application development. Unlike Frappe, which prioritizes configuration or visual tools, Laravel emphasizes writing expressive, clean codes to build and customize applications.
Diving into Laravel’s code-centric approach:
Laravel’s Philosophy
Laravel follows the Model-View-Controller (MVC) architectural pattern, which separates an application into three primary logical components. This separation of concerns allows developers to have fine-grained control over each aspect of their application.
Key Components of Laravel’s Structure
- Controllers: Handle the logic of processing requests and returning responses.
- Models: Represent the data structure and handle database interactions.
- Views: Manage the presentation layer of the application.
- Routes: Define the URLs and HTTP methods that the application responds to.
How These Components Work Together
In Laravel, these components work in harmony to create a flexible and powerful application structure:
- Routes direct incoming requests to the appropriate controller methods.
- Controllers process the request, interact with models to fetch or manipulate data, and return a response (often a view).
- Models sum up the business logic and data access layer.
- Views render the final output to be sent back to the user.
Laravel’s code-centric approach offers several advantages:
- Granular Control: Developers can fine-tune every aspect of their application’s behavior.
- Customization: Easy to add custom logic at any point in the request lifecycle.
- Testability: The separation of concerns makes it easier to write unit and integration tests.
- Scalability: As the application grows, it’s easier to maintain and extend the codebase.
Let’s create a simple “Task” management system to demonstrate the process of setting up routes, controllers, and views. Let’s break this down into steps:
- Set up the routes
- Create the model
- Generate the controller
- Create the views
Basic CRUD module for managing tasks in Laravel.
Here’s a rundown of what each part does:
- Routes: We define RESTful routes for all CRUD operations on tasks.
- Model: We create a
`Task`
model that represents our database table and defines the fillable fields. - Controller: The
`TaskController`
contains methods for each CRUD operation:`index()`
: Display all tasks`create()`
: Show the form to create a new task`store()`
: Save a new task`show()`
: Display a specific task`edit()`
: Show the form to edit an existing task`update()`
: Update an existing task`destroy()`
: Delete a task
- Views: We create Blade templates for displaying, creating, and editing tasks. The example shows the index, create, and edit views.
To use this CRUD module:
- Create the necessary database migration for the tasks table.
- Copy the routes into your
`web.php`
file.
Routes (web.php)
Define the routes for your CRUD operations
1 2 3 4 5 6 7 8 9 |
use AppHttpControllersTaskController; Route::get('/tasks', [TaskController::class, 'index'])->name('tasks.index'); Route::get('/tasks/create', [TaskController::class, 'create'])->name('tasks.create'); Route::post('/tasks', [TaskController::class, 'store'])->name('tasks.store'); Route::get('/tasks/{task}', [TaskController::class, 'show'])->name('tasks.show'); Route::get('/tasks/{task}/edit', [TaskController::class, 'edit'])->name('tasks.edit'); Route::put('/tasks/{task}', [TaskController::class, 'update'])->name('tasks.update'); Route::delete('/tasks/{task}', [TaskController::class, 'destroy'])->name('tasks.destroy'); |
Now, let’s break down and discuss this code:
Controller Import:
1 |
use App\Http\Controllers\TaskController; |
This line imports the TaskController
class. It’s using PHP’s namespace system, which Laravel leverages for organizing code. The `App\Http\Controllers`
namespace is where Laravel conventionally places controller classes.
RESTful Route Definitions:
The subsequent lines define routes for a RESTful API for managing tasks. Each route maps an HTTP method and URL to a specific method in the TaskController
.
Route::get('/tasks', [TaskController::class, 'index'])->name('tasks.index');
This defines a GET route to/tasks
, which will call theindex
method ofTaskController
. It’s typically used to list all tasks.Route::get('/tasks/create', [TaskController::class, 'create'])->name('tasks.create');
This route is for displaying a form to create a new task.Route::post('/tasks', [TaskController::class, 'store'])->name('tasks.store');
ThisPOST
route is used to handle the submission of the create form and store a new task.Route::get('/tasks/{task}', [TaskController::class, 'show'])->name('tasks.show');
This route is for showing details of a specific task. The{task}
is a route parameter that Laravel will automatically resolve to a Task model instance.Route::get('/tasks/{task}/edit', [TaskController::class, 'edit'])->name('tasks.edit');
This route is for displaying a form to edit an existing task.Route::put('/tasks/{task}', [TaskController::class, 'update'])->name('tasks.update');
ThisPUT
route handles the submission of the edit form to update an existing task.Route::delete('/tasks/{task}', [TaskController::class, 'destroy'])->name('tasks.destroy');
ThisDELETE
route is for removing a task.
Route Names:
Each route is given a name using the ->name()
method. This is a powerful feature in Laravel that allows you to refer to routes by name rather than URL in your application. For example, you can use route('tasks.index')
in your views or controllers to generate the URL for the tasks index page.
Route Parameters:
The {task}
in some routes is a route parameter. Laravel will automatically inject the corresponding Task model instance into the controller method, a feature known as Route Model Binding .
HTTP Verbs:
The routes use different HTTP verbs (GET
, POST
, PUT
, DELETE
) which correspond to different CRUD operations. This adheres to RESTful principles .
6. Resource Routes:
It’s worth noting that Laravel provides a shortcut for defining all these routes in one line using resource routing:
1 |
Route::resource('tasks', TaskController::class); |
This would create all the routes we see here, plus a couple more.
Create the `Task`
model in `app/Models/Task.php`
.
Model (Task.php)
Define the Task model
1 2 3 4 5 6 7 8 9 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Task extends Model { use HasFactory; protected $fillable = ['title', 'description', 'status']; } |
Breaking down this Laravel model definition:
Namespace Declaration:
1 |
namespace App\Models; |
This line declares the namespace for the Task class. In Laravel, models are typically placed in the App\Models namespace, which corresponds to the app/Models
directory in the project structure.
Use Statements:
1 2 |
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; |
These lines import the `HasFactory`
trait and the base `Model`
class from Laravel’s Eloquent ORM. Eloquent is Laravel’s built-in ORM (Object-Relational Mapping) system .
Class Definition:
1 |
class Task extends Model |
This line defines the Task
class, which extends Laravel’s base Model
class. By extending Model
, Task
inherits all the functionality provided by Eloquent, such as database interactions, relationships, and more .
Traits:
1 |
use HasFactory; |
This line includes the HasFactory
trait in the Task
model. The HasFactory
trait allows you to use model factories with this model, which is useful for generating fake data in tests or database seeding.
Fillable Property:
1 |
protected $fillable = ['title', 'description', 'status']; |
The $fillable
property is an array that specifies which attributes can be mass-assigned. This is a security feature in Laravel to prevent unintended mass assignment vulnerabilities. Only the attributes listed in this array can be set via mass assignment methods like create()
or update()
.
Key Points to Note:
- By default, Laravel will assume the table name for this model is the snake case plural form of the class name. In this case, it would be
tasks
. If you need a different table name, you can specify it using the$table
property. - Laravel uses conventions to automatically determine many aspects of how the model interacts with the database. For example, it assumes there’s an auto-incrementing id column as the primary key and
created_at
andupdated_at
timestamp columns for tracking record creation and updates. - The
HasFactory
trait allows you to create a corresponding factory for this model, which can be used to generate fake Task instances for testing or seeding your database. - The
$fillable
property is crucial for mass assignment protection. Any attributes not listed here will be guarded from mass assignment, helping to prevent potential security vulnerabilities.
This Task model provides a simple yet powerful interface to interact with the tasks
table in your database, leveraging Laravel’s Eloquent ORM for database operations, query building, and more.
- Generate the
TaskController
usingphp artisan make:controller TaskController --resource`
and copy the provided methods.
Controller (TaskController.php)
Define the CRUD operations in the controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<?php namespace App\Http\Controllers; use App\Models\Task; use Illuminate\Http\Request; class TaskController extends Controller { public function index() { $tasks = Task::all(); return view('tasks.index', compact('tasks')); } public function create() { return view('tasks.create'); } public function store(Request $request) { $validatedData = $request->validate([ 'title' => 'required|max:255', 'description' => 'required', 'status' => 'required|in:pending,in_progress,completed', ]); Task::create($validatedData); return redirect()->route('tasks.index')->with('success', 'Task created successfully.'); } public function show(Task $task) { return view('tasks.show', compact('task')); } public function edit(Task $task) { return view('tasks.edit', compact('task')); } public function update(Request $request, Task $task) { $validatedData = $request->validate([ 'title' => 'required|max:255', 'description' => 'required', 'status' => 'required|in:pending,in_progress,completed', ]); $task->update($validatedData); return redirect()->route('tasks.index')->with('success', 'Task updated successfully.'); } public function destroy(Task $task) { $task->delete(); return redirect()->route('tasks.index')->with('success', 'Task deleted successfully.'); } } |
Now, let’s break down this controller and discuss its key components:
Namespace and Use Statements:
1 2 3 |
namespace App\Http\Controllers; use App\Models\Task; use Illuminate\Http\Request; |
The controller is in the App\Http\Controllers
namespace, which is the standard location for controllers in Laravel. It imports the Task
model and the Request
class, which are used throughout the controller.
Index Method:
1 2 3 4 5 |
public function index() { $tasks = Task::all(); return view('tasks.index', compact('tasks')); } |
This method retrieves all tasks from the database using Task::all()
and passes them to the tasks.index
view. The compact()
function is used to pass variables to the view.
Create Method:
1 2 3 4 |
public function create() { return view('tasks.create'); } |
This method simply returns the view for creating a new task.
Store Method:
1 2 3 4 5 6 7 8 9 10 |
public function store(Request $request) { $validatedData = $request->validate([ 'title' => 'required|max:255', 'description' => 'required', 'status' => 'required|in:pending,in_progress,completed', ]); Task::create($validatedData); return redirect()->route('tasks.index')->with('success', 'Task created successfully.'); } |
This method handles the creation of a new task. It first validates the incoming request data, creates a new task with the validated data, and then redirects to the index page with a success message .
Show Method:
1 2 3 4 |
public function show(Task $task) { return view('tasks.show', compact('task')); } |
This method displays a specific task. Laravel’s route model binding is used here, automatically fetching the Task model instance based on the route parameter .
Edit Method:
1 2 3 4 |
public function edit(Task $task) { return view('tasks.edit', compact('task')); } |
This method returns the view for editing a specific task.
Update Method:
1 2 3 4 5 6 7 8 9 10 |
public function update(Request $request, Task $task) { $validatedData = $request->validate([ 'title' => 'required|max:255', 'description' => 'required', 'status' => 'required|in:pending,in_progress,completed', ]); $task->update($validatedData); return redirect()->route('tasks.index')->with('success', 'Task updated successfully.'); } |
This method handles updating an existing task. It validates the incoming data, updates the task, and redirects to the index page with a success message.
Destroy Method:
1 2 3 4 5 |
public function destroy(Task $task) { $task->delete(); return redirect()->route('tasks.index')->with('success', 'Task deleted successfully.'); } |
This method deletes a specific task and redirects to the index page with a success message.
Key Points:
- The controller follows RESTful conventions, with methods corresponding to standard CRUD operations.
- Laravel’s route model binding is used in several methods (
show
,edit
,update
,destroy
), automatically injecting the Task model instance. - The
validate()
method is used for input validation instore
andupdate
methods, ensuring data integrity. - Redirect responses are used after successful operations, often with flash messages added via the
with()
method. - The controller uses Laravel’s naming conventions for views, assuming they exist in a
resources/views/tasks
directory. - Create the view files in
`resources/views/tasks/`
.
Views
Create Blade templates for each CRUD operation
index.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@extends('layouts.app') @section('content') <h1>Task List</h1> <a href="{{ route('tasks.create') }}">Create New Task</a> @foreach ($tasks as $task) <div> <h2>{{ $task->title }}</h2> <p>{{ $task->description }}</p> <p>Status: {{ $task->status }}</p> <a href="{{ route('tasks.show', $task) }}">View</a> <a href="{{ route('tasks.edit', $task) }}">Edit</a> <form action="{{ route('tasks.destroy', $task) }}" method="POST"> @csrf @method('DELETE') <button type="submit">Delete</button> </form> </div> @endforeach @endsection |
create.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@extends('layouts.app') @section('content') <h1>Create New Task</h1> <form action="{{ route('tasks.store') }}" method="POST"> @csrf <div> <label for="title">Title:</label> <input type="text" name="title" id="title" required> </div> <div> <label for="description">Description:</label> <textarea name="description" id="description" required></textarea> </div> <div> <label for="status">Status:</label> <select name="status" id="status" required> <option value="pending">Pending</option> <option value="in_progress">In Progress</option> <option value="completed">Completed</option> </select> </div> <button type="submit">Create Task</button> </form> @endsection |
edit.blade.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@extends('layouts.app') @section('content') <h1>Edit Task</h1> <form action="{{ route('tasks.update', $task) }}" method="POST"> @csrf @method('PUT') <div> <label for="title">Title:</label> <input type="text" name="title" id="title" value="{{ $task->title }}" required> </div> <div> <label for="description">Description:</label> <textarea name="description" id="description" required>{{ $task->description }}</textarea> </div> <div> <label for="status">Status:</label> <select name="status" id="status" required> <option value="pending" {{ $task->status == 'pending' ? 'selected' : '' }}>Pending</option> <option value="in_progress" {{ $task->status == 'in_progress' ? 'selected' : '' }}>In Progress</option> <option value="completed" {{ $task->status == 'completed' ? 'selected' : '' }}>Completed</option> </select> </div> <button type="submit">Update Task</button> </form> @endsection |
Remember to style your views, add error handling, and implement authentication and authorization as needed for a production-ready application.
This example provides a solid foundation for creating CRUD modules in Laravel. You can extend and customize it further based on your specific requirements.
Let’s discuss each of these Blade templates:
index.blade.php:
- This template displays the list of all tasks.
- It extends a layout file (
layouts.app
), which likely contains the common structure for all pages. - It uses a
@foreach
loop to iterate through all tasks and display their details. - For each task, it shows the title, description, and status.
- It provides links to view, edit, and delete each task.
- The delete functionality is implemented as a form to allow for proper
HTTP DELETE
requests. - It uses Laravel’s
route()
helper function to generate URLs, which helps maintain clean routes throughout the application.
create.blade.php:
- This template provides a form for creating a new task.
- It also extends the
'layouts.app'
layout. - The form posts to the
'tasks.store'
route, which will be handled by the store method in theTaskController
. - It includes the
@csrf
directive forCSRF
protection, which is crucial for form security in Laravel. - The form includes fields for title, description, and status, matching the structure of our
Task
model. - The
status
field is a select dropdown with predefined options.
edit.blade.php:
- This template is similar to
create.blade.php
, but it’s for editing an existing task. - It populates the form fields with the current task’s data.
- The form posts to the
'tasks.update'
route, including the taskID
. - It uses the
@method('PUT')
directive to specify that this is anHTTP PUT
request for updating the resource. - The status dropdown uses inline PHP to mark the current status as selected.
Key points to note:
- All templates use Laravel’s Blade syntax, which allows for easy integration of PHP code within HTML.
- They make use of Laravel’s routing system and helper functions like route().
- The templates demonstrate proper handling of different HTTP methods (GET, POST, PUT, DELETE) for RESTful operations.
- Form security is maintained through the use of CSRF tokens.
- The templates are designed to work seamlessly with the TaskController methods we discussed earlier.
Pros and Cons Comparison: Configuration vs. Code
I’ll provide a comparison of the pros and cons between Frappe’s configuration-based approach and Laravel’s code-based approach, focusing on speed of development and flexibility/control.
For speed of development
In this section we will look at how each of the tools will typically be better for you when it comes to time to complete the task you are trying to accomplish.
Frappe (Configuration-based):
Pros:
- There’s faster initial setup due to built-in admin interface and automatic CRUD operations
- It is excellent for quick prototyping, allowing developers to create functional applications with minimal code
- It has a shorter learning curve for basic applications, making it easier for non-programmers or junior developers to get started
Cons:
- It may slow down development for highly custom features that don’t fit into the predefined structure
- The limited customization options can lead to time-consuming workarounds for specific requirements
Laravel (Code-based):
Pros:
- There’s faster development of complex, custom features due to full control over the codebase
- An extensive ecosystem of packages and libraries that can speed up feature implementation
- It is efficient for experienced developers who can leverage Laravel’s powerful tools and conventions
Cons:
- It has a steeper learning curve, especially for beginners or those new to MVC frameworks
- There’s more initial setup time required to create the basic structure of the application
- It may require more time for simple CRUD operations compared to Frappe’s automatic generation
For flexibility and control
Sometimes, you need to prioritize configuring a project like you need it and you can sacrifice speed of development. Let’s see how the two tools compare to one another.
Frappe (Configuration-based):
Pros:
- It is excellent for standard business operations with built-in functionality
- There’s consistent structure across projects, making it easier for team members to understand and maintain
- It allows rapid implementation of common features without writing extensive code
Cons:
- A limited flexibility for implementing highly specific business logic
- It is constrained by the framework’s predefined structures and workflows
- It may require workarounds or custom scripts for non-standard features, which can become complex and hard to maintain
Laravel (Code-based):
Pros:
- It is highly customizable, allowing developers to implement any business logic or feature
- There’s full control over the application architecture and design patterns
- It easily handles complex use cases and integrations with third-party services
- There’s greater flexibility in choosing and implementing design patterns suitable for specific project needs
Cons:
- It requires more expertise to properly architect and implement complex systems
- There’s higher risk of inconsistencies across projects if coding standards are not strictly followed
- It may lead to over-engineering if not properly managed, especially for simpler applications
Conclusion
The choice between Frappe and Laravel depends on the significant needs of the project and the development team’s expertise. Frappe excels in rapid prototyping and development of standard business applications, making it ideal for projects with tight deadlines or those requiring minimal customization. Laravel, on the other hand, shines in projects that demand high levels of customization, complex business logic, or scalability for future growth. The decision should be based on factors such as project complexity, team expertise, time constraints, and long-term maintenance considerations.
The next part of this article will explore comparison between Database Management with Frappe’s DocTypes and Laravel’s Migration System.
Load comments