My experience of moving from Laravel to Django

Before my current role, I spent most of my professional development career writing web apps in Laravel. I learned Laravel out of necessity after learning PHP, but wanted to avoid working in WordPress – and I quickly grew fond of the framework. Even during Laravel 5, the framework just felt right.

It was highly opinionated (which was crucial as a new developer, trying to learn the best way to structure large scale, multi-faceted apps), had robust documentation that was extremely easy to follow, and had very clear design patterns that made it easy to interface with the ORM and concepts like Facades. When scaffolding out a new Laravel project, namespaces were clear and it was easy to see how different parts of the app interacted with each other. Laravel's use of Artisan was also a major developer experience boost – you could generate new controllers, models, notifications, and more – without having to rely on any other external tools or questioning if it's structured properly. It just worked.

Later on, I moved to using Django as my primary framework. I figured that the transition between Laravel to Django would be simple, but even some of the more basic concepts gave me trouble regarding terminology and patterns.

Why Django?

Whenever I was looking for new roles at the beginning of 2021, I ended up interviewing for a company (where I currently work) that was using Django as a large part of its stack. Consistently, I was seeing more opportunities for Django and other frameworks revolving around Python and felt that I may have been inhibiting my growth by staying on the Laravel track.

Even though I had used Flask before in several freelance projects, Django felt like a completely different beast. Flask touts itself as a "micro-framework" and compared to full-fledged frameworks like Django or Laravel, that's exactly what it is. It's very convenient for creating more directed projects quickly but falls short with tooling.

I had a preliminary interview and immediately began learning Django. Despite making it clear that I hadn't used Django before, I wanted to get as much knowledge of the framework as possible so that I wouldn't be subject to a major learning curve before beginning my new position.

I didn't want to put all of my eggs into one basket, so I kept the sample project I was working on small. It had to be just enough that I covered the major concepts of Django, but with enough complexity that it couldn't be classified as a "to-do list". At the time, I wanted a way of tracking inventory in my home office, so I built an incomplete inventory tracking system after following the Django docs. Despite having a lot more experience with the framework now, I still reference this project sometimes when I'm implementing new viewsets (more on this later).

Side note: If you really want to work at a company and don't have a solid grasp on the tech that they're using, pour your time into it (within reason). While it's perfectly okay to learn on the job (as I did, and still do), being able to hit the ground running and contribute is personally incredibly rewarding.

The parallels between Laravel and Django

While this won't be a detailed technical writeup of the two, there are some core concepts that exist in parallel between Laravel and Django. To start, Laravel and Django both follow the Model-view-controller (MVC) design pattern, which is a common way of developing both desktop and web applications.

Freedom to choose

Though they both follow MVC, Laravel feels more "strict" in enforcing certain patterns. If you've ever taken a look at the Laravel documentation or inside of a Laravel project, you'll see that there are a lot of different features of the framework that are already abstracted out for you. At a minimum, you run php artisan and generate the components that you need – at maximum, you have to do some configuration to make it fit eloquently into the rest of your app. I use Laravel's documentation as a gold standard in framework + technical documentation and wish more would follow suit.

Contrarily, I have always felt that Django gives you a bit more freedom to make your own decisions on how you want to structure your app, what namespaces (module directories) to use, etc. Django's documentation isn't fantastic compared to Laravel, but it's enough that you can reference and implement different concepts.

Building an API

Django also provides you with an "admin" view, which is essentially a CRUD panel for any models that you load. This is probably a better choice for someone looking for a framework with an admin dash prebuilt, but I use both Laravel and Django in the context of building an API only. With Django, you'd need to pull in Django REST framework (DRF) – Laravel offers it right out of the box.

Tinkering with data

Both Laravel and Django have a CLI tool that allows you to use parts of your app in isolation and interact with the database. This is really useful when you want to see what certain methods of the query builders do, or one-off functions that would take more writing in a controller/viewset. I personally feel that php artisan tinker is easier to use and integrated more closely with the framework. It's an incredibly tiny detail, but tinker utilizes autoloading and allows you to reference models by namespaces, without having to import them. This is different from the Django manage.py shell, where you have to import all of the modules and classes that you want to interact with.

Where my confusion lied

Following the MVC pattern, Laravel and Django both have models, views, and controllers. However, I felt that Django was a lot less clear in designating what maps to what, at least coming from a traditional background. Let me explain:

Laravel

Two majors concepts of building APIs in Laravel are models and controllers.

  • Model: A blueprint for objects, which is just a class that allows you to specify a different table name, primary key, attributes, etc. Unlike Django, defining fields is abstracted away to migration, and Laravel separates the Eloquent instance from the database representation.
<?php
// App\Models\Employee.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    //
}
  • Controller: Typically, controllers return model instances with minimal processing. These are where you handle defining CRUD operations for your endpoints (web or API). Unlike Django's optional functional approach, these are always classes.
<?php
// App\Http\Controllers\API\EmployeeController.php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Employee;

class EmployeeController extends Controller
{
    public function show($id)
    {
        return response()->json(Employee::findOrFail($id));
    }
}
  • Views: Laravel views are Blade templates that are separated from your business logic, and only hold the presentation layer. These are used to display data provided from controllers – the use of "views" is going to be an important distinction in Django, where these are called "templates" in a Django context.

When defining an API route, you only need to reference the method of an API controller. You don't need to touch "views" at all, and in a lot of my projects, I never created a single view (I used a separate React/Vue app for this).

Django

On the other hand, Django uses the concepts of models, serializers, and views (which can be called viewsets). Even for API routes, all three of these are required.

  • Model: The model is similar to Laravel's definition, but must contain the fields and metadata at a minimum. If you want to define any custom attributes or model-specific business logic, this must be done in a serializer. On one hand, it's easier to follow and practices better separation of concerns – on the other hand, it's another file to create, maintain, and reference.
# app/models/employee.py
from django.db import models

class Employee(models.Model):
  name = models.CharField(max_length=255)
  age = models.PositiveSmallIntegerField(null=True)
  • Serializer: Serializers are going to be your definition for what a model will return. You have the ability to include/exclude fields from responses, define nested representations (show the entire User model, instead of just a user_id ), and create method fields that will return some value every time your object is being returned. Essentially, splitting a Laravel model returns a Django model + serializer. A serializer is technically an offering of DRF, but if you're building an API, you'll have this.
# app/serializers/employee.py
from rest_framework import serializers

from app.models import Employee

class EmployeeSerializer(serializers.ModelSerializer):
  class Meta:
    model = Employee
    fields = ['name', 'age']
  • Views/viewsets: Django views are Laravel's controllers. Here, you can define individual functions (similar to single-action controllers in Laravel), or entire viewsets which hold related methods (controllers). The use of "views" as terminology for a controller was one of the hardest things to wrap my head around. Below is a view built using DRF, which differs slightly from a generic Django view.
# app/views/employee.py
from rest_framework import generics

from app.models import Employee
from app.serializers import EmployeeSerializer

class EmployeeList(generics.ListCreateAPIView):
  queryset = Employee.objects.all()
  serializer_class = EmployeeSerializer
  • Templates: The template layer is Django's presentational "view". This is where data from your views will be rendered to the user and is analogous to Laravel's views.

The winner

There are other design patterns and features that I didn't dive into here, such as queues and signals/listeners. The usage of both is very different between Laravel and Django, where Django feels more primitive, but that flexibility is what makes it a great choice for many applications. Some may feel that Laravel is too opinionated or enforces too many rules, but that's what's great about it – it's hard to break out of the design pattern, which makes code more consistent and makes it easier to onboard new developers onto a project.

Personally, I reach for Laravel's ideas in every project, regardless of what language I'm using. The structure/flow is easy to follow and the documentation is fantastic. When I was using Laravel professionally, I enjoyed it a lot. Being able to use queues, caching, events, and notifications right out of the box made it easy to develop complex features without thinking about what other tools I'd have to pull in. There's a little bit more work involved with Django, but you also get to define your own starting point and make decisions how you see fit. In terms of performance, Django is probably a better choice, but everything comes down to what you need in an application.

tl;dr

Laravel adheres more closely to traditional MVC – models define business logic and attributes, controllers are used for endpoints and data manipulation, and views provide the presentation layer.

Django increases complexity a bit by using different terminology – a traditional "model" is essentially a model + a serializer, views/viewsets are traditional controllers, and templates are traditional "views" that provide presentation.

16