<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/catalyst/</id>
  <link href="https://www.endpointdev.com/blog/tags/catalyst/"/>
  <link href="https://www.endpointdev.com/blog/tags/catalyst/" rel="self"/>
  <updated>2025-02-06T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Django and Mojolicious: a quick comparison of two popular web frameworks</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/02/django-mojolicious/"/>
      <id>https://www.endpointdev.com/blog/2025/02/django-mojolicious/</id>
      <published>2025-02-06T00:00:00+00:00</published>
      <author>
        <name>Marco Pessotto</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/02/django-mojolicious/architecture-structure-wood-building-beam-construction-1063818-pxhere.webp&#34; alt=&#34;A view upward toward the wooden framing of a house under construction against a blue sky.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo https://pxhere.com/en/photo/1063818 CC0 Public Domain --&gt;
&lt;p&gt;Recently I&amp;rsquo;ve been working on a project with a &lt;a href=&#34;https://vuejs.org/&#34;&gt;Vue&lt;/a&gt; front-end and two back-ends, one in Python using the &lt;a href=&#34;https://www.djangoproject.com/&#34;&gt;Django&lt;/a&gt; framework and one in Perl using the &lt;a href=&#34;https://www.mojolicious.org/&#34;&gt;Mojolicious&lt;/a&gt; framework. So, it&amp;rsquo;s a good time to spend some words to share the experience and do a quick comparison.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2022/04/perl-web-frameworks/&#34;&gt;Previously&lt;/a&gt; I wrote a post about Perl web frameworks, and now I&amp;rsquo;m expanding the subject into another language.&lt;/p&gt;
&lt;p&gt;Django was chosen for this project because it&amp;rsquo;s been around for almost 20 years now and provides the needed maturity and stability to be long-running and low-budget. In this regard, it has proved a good choice so far. Recently it saw a major version upgrade without any problems to speak of. It could be argued that I should have used the &lt;a href=&#34;https://github.com/encode/django-rest-framework&#34;&gt;Django REST Framework&lt;/a&gt; instead of plain Django. However, at the time the decision was made, adding a framework on top of another seemed a bit excessive. I don&amp;rsquo;t have many regrets about this, though.&lt;/p&gt;
&lt;p&gt;Mojolicious is an old acquaintance. It used to have fast-paced development but seems very mature now, and it&amp;rsquo;s even been &lt;a href=&#34;https://mojojs.org/&#34;&gt;ported&lt;/a&gt; to JavaScript.&lt;/p&gt;
&lt;p&gt;Both frameworks have just a few dependencies (which is fairly normal in the Python world, but not in the Perl one) and excellent documentation. They both follow the model-view-controller pattern. Let&amp;rsquo;s examine the components.&lt;/p&gt;
&lt;h3 id=&#34;views&#34;&gt;Views&lt;/h3&gt;
&lt;p&gt;Both frameworks come with a built-in template system (which can be swapped out with something else), but in this project we can skip the topic altogether as both frameworks are used only as back-end for transmitting JSON, without any HTML rendering involved.&lt;/p&gt;
&lt;p&gt;However, let&amp;rsquo;s see how the rendering looks for the API we&amp;rsquo;re writing.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Base&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Mojolicious::Controller&amp;#39;&lt;/span&gt;, -signatures;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;check&lt;/span&gt; ($self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;render(json =&amp;gt; { status =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;OK&amp;#39;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;django.http&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; JsonResponse
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;status&lt;/span&gt;(request):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; JsonResponse({ &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;:  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;OK&amp;#34;&lt;/span&gt; })&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Nothing complicated here, just provide the right call.&lt;/p&gt;
&lt;h3 id=&#34;models&#34;&gt;Models&lt;/h3&gt;
&lt;h4 id=&#34;django&#34;&gt;Django&lt;/h4&gt;
&lt;p&gt;Usually a model in context of web development means a database and here we are going to keep this assumption.&lt;/p&gt;
&lt;p&gt;Django comes with a comprehensive &lt;a href=&#34;https://docs.djangoproject.com/en/5.1/topics/db/queries/&#34;&gt;object-relational mapping&lt;/a&gt; (ORM) system and it feels like the natural thing to use. I don&amp;rsquo;t think it makes much sense to use another ORM, or even to use raw SQL queries (though it is &lt;a href=&#34;https://docs.djangoproject.com/en/5.1/topics/db/sql/&#34;&gt;possible&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You usually start a Django project by defining the model. The Django ORM gives you the tools to manage the migrations, providing abstraction from the SQL. You need to define the field types and the relationships (joins and foreign keys) using the appropriate class methods.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;django.db&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; models
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;User&lt;/span&gt;(AbstractUser):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    email = models.EmailField(null=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;False&lt;/span&gt;, blank=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    site = models.ForeignKey(Site, on_delete=models.CASCADE, related_name=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;site_users&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    libraries = models.ManyToManyField(Library, related_name=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;affiliated_users&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expiration = models.DateTimeField(null=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;, blank=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    created = models.DateTimeField(auto_now_add=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    last_modified = models.DateTimeField(auto_now=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These calls provide not only the SQL type to use, but also the validation. For example, the &lt;code&gt;blank&lt;/code&gt; parameter is a validation option specifying whether Django will accept an empty value. It is different from the &lt;code&gt;null&lt;/code&gt; option, which directly correlates to SQL. You can see we&amp;rsquo;re quite far from working with SQL, at least two layers of abstraction away.&lt;/p&gt;
&lt;p&gt;In the example above, we&amp;rsquo;re also defining a foreign key between a site and a user (many-to-one), so each user belongs to one site. We also define a many-to-many relationship with the libraries record. I like how these relationships are defined, it&amp;rsquo;s very concise.&lt;/p&gt;
&lt;p&gt;Thanks to these definitions, you get a whole &lt;a href=&#34;https://docs.djangoproject.com/en/5.1/ref/contrib/admin/&#34;&gt;admin console&lt;/a&gt; almost for free, which your admin users are sure to like. However, I&amp;rsquo;m not sure this is a silver bullet for solving all problems. With large tables and relationships the admin pages load slowly and they could become unusable very quickly. Of course, you can tune that by filtering out what you need and what you don&amp;rsquo;t, but that means things are not as simple as &amp;ldquo;an admin dashboard for free&amp;rdquo; — at the very least, there&amp;rsquo;s some configuring to do.&lt;/p&gt;
&lt;p&gt;As for the query syntax, you usually need to call &lt;code&gt;Class.objects.filter()&lt;/code&gt;. As you would expect from an ORM, you can chain the calls and finally get objects out of that, representing a database row, which, in turn, you can update or delete.&lt;/p&gt;
&lt;p&gt;The syntax for the &lt;code&gt;filter()&lt;/code&gt; call is based on the double underscore separator, so you can query over the relationships like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; agent &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; (Agent.objects.filter(canonical_agent_id__isnull=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;False&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              .prefetch_related(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;canonical_agent&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              .order_by(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;canonical_agent__name&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;name&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              .all()):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    agent.name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Dummy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    agent.save()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In this case, provided that we defined the foreign keys and the attributes in the model, we can search/​order across the relationship. The &lt;code&gt;__isnull&lt;/code&gt; suffix, as you can imagine, results in a &lt;code&gt;WHERE canonical_agent_id IS NOT NULL&lt;/code&gt; query, while in the &lt;code&gt;order_by&lt;/code&gt; call we sort over the joined table using the &lt;code&gt;name&lt;/code&gt; column. Looks nice and readable, with a touch of magic.&lt;/p&gt;
&lt;p&gt;Of course things are never so simple, so you can build complex queries with the &lt;code&gt;Q&lt;/code&gt; class combined with bytewise operators (&lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;|&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of a simple case-insensitive search for a name containing multiple words:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;django.db.models&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; Q
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;api_list&lt;/span&gt;(request)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    term = request.GET.get(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;search&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; term
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        words = [ w &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; w &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; re.split(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;\W+&amp;#39;&lt;/span&gt;, term) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; w ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; words:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            query = Q(name__icontains=words.pop())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;while&lt;/span&gt; words:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                query = query &amp;amp; Q(name__icontains=words.pop())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#888&#34;&gt;# logger.debug(query)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            agents = Agent.objects.filter(query).all()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To sum up, the ORM is providing everything you need to stay away from the SQL. In fact, it seems like Django doesn&amp;rsquo;t like you doing raw SQL queries.&lt;/p&gt;
&lt;h4 id=&#34;mojolicious-and-perl&#34;&gt;Mojolicious and Perl&lt;/h4&gt;
&lt;p&gt;In the Perl world things are a bit different.&lt;/p&gt;
&lt;p&gt;The Mojolicious &lt;a href=&#34;https://docs.mojolicious.org/Mojolicious/Guides/Tutorial&#34;&gt;tutorial&lt;/a&gt; doesn&amp;rsquo;t even mention the database. You can use any ORM or no ORM at all, if you prefer so. However, Mojolicious makes the DB handle available everywhere in the application.&lt;/p&gt;
&lt;p&gt;You could use &lt;a href=&#34;https://metacpan.org/pod/DBIx::Connector&#34;&gt;DBIx::Connector&lt;/a&gt;, &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class&#34;&gt;DBIx::Class&lt;/a&gt;, &lt;a href=&#34;https://docs.mojolicious.org/Mojo/Pg&#34;&gt;Mojo::Pg&lt;/a&gt; (which was developed with Mojolicious), or whatever you prefer.&lt;/p&gt;
&lt;p&gt;For example, to use Mojo::Pg in the main application class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyApp&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Base&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Mojolicious&amp;#39;&lt;/span&gt;, -signatures;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Pg&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Data::Dumper::Concise&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;startup&lt;/span&gt; ($self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$config&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;plugin(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;NotYAMLConfig&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;log&lt;/span&gt;-&amp;gt;info(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Starting up with &amp;#34;&lt;/span&gt; . Dumper(&lt;span style=&#34;color:#369&#34;&gt;$config&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;helper(pg =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      state &lt;span style=&#34;color:#369&#34;&gt;$pg&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Pg&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$config&lt;/span&gt;-&amp;gt;{dbi_connection_string});
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the routes you can call &lt;code&gt;$self-&amp;gt;pg&lt;/code&gt; to get the database object.&lt;/p&gt;
&lt;p&gt;The three approaches I&amp;rsquo;ve mentioned here are different.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DBIx::Connector&lt;/code&gt; is basically a way to get you a safe DBI handle across forks and DB connection failures.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Mojo::Pg&lt;/code&gt; gives you the ability to do abstract queries but also gives some convenient methods to get the results. I wouldn&amp;rsquo;t call it a ORM; from a query you usually gets hashes, not objects, you don&amp;rsquo;t need to define the database layout, and it won&amp;rsquo;t produce migrations for you, though there is some &lt;a href=&#34;https://docs.mojolicious.org/Mojo/Pg/Migrations&#34;&gt;migration support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of standard and abstract queries:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;list_texts&lt;/span&gt; ($self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sid&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;param(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sid&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sql&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;SELECT * FROM texts WHERE sid = ? ORDER BY sorting_index&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#369&#34;&gt;@all&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;pg&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;db&lt;/span&gt;-&amp;gt;query(&lt;span style=&#34;color:#369&#34;&gt;$sql&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$sid&lt;/span&gt;)-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hashes&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;each&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;render(json =&amp;gt; { texts =&amp;gt; \&lt;span style=&#34;color:#369&#34;&gt;@all&lt;/span&gt; });&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The query above can be rewritten with an abstract query, using the same module.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;@all&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;pg&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;db&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;select&lt;/span&gt;(texts =&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;undef&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             { sid =&amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$sid&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                             { order_by =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;sorting_index&amp;#39;&lt;/span&gt; })-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;hashes&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;each&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If it&amp;rsquo;s a simple, static query, it&amp;rsquo;s basically a matter of taste; do you prefer to see the SQL or not? The second version is usually nicer if you want to build a different query depending on the parameters, so you add or remove keys to the hashes which maps to query and finally execute it.&lt;/p&gt;
&lt;p&gt;Now, speaking of taste, for complex queries with a lot of joins I honestly prefer to see the SQL query instead of wondering if the abstract one is producing the correct SQL. This is true regardless of the framework. I have the impression that it is faster, safer, and cleaner to have the explicit SQL in the code rather than leaving future developers (including future me) to wonder if the magic is happening or not.&lt;/p&gt;
&lt;p&gt;Finally, nothing stops you from using &lt;code&gt;DBIx::Class&lt;/code&gt;, which is the best ORM for Perl, even if it&amp;rsquo;s not exactly light on dependencies.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s very versatile, it can build queries of arbitrary complexity, and you usually get objects out of the queries you make. It doesn&amp;rsquo;t come with an admin dashboard, it doesn&amp;rsquo;t enforce the data types and it doesn&amp;rsquo;t ship any validation by default (of course, you can implement that manually). The query syntax is very close to the &lt;code&gt;Mojo::Pg&lt;/code&gt; one (which is basically &lt;a href=&#34;https://metacpan.org/pod/SQL::Abstract&#34;&gt;SQL::Abstract&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The gain here is that, like in Django&amp;rsquo;s ORM, you can attach your methods to the classes representing the rows, so the data definitions live with the code operating on them.&lt;/p&gt;
&lt;p&gt;However, the fact that it builds an object for each result means you&amp;rsquo;re paying a performance penalty which sometimes can be very high. I think this is a problem common to all ORMs, regardless of the language and framework you&amp;rsquo;re using.&lt;/p&gt;
&lt;p&gt;The difference with Django is that once you have chosen it as your framework, you are basically already sold to the ORM. With Mojolicious and other Perl frameworks (Catalyst, Dancer), you can still make the decision and, at least in theory, change it down the road.&lt;/p&gt;
&lt;p&gt;My recommendation would be to keep the model, both code and business logic, decoupled from the web-specific code. This is not really doable with Django, but is fully doable with the Perl frameworks. Just put the DB configuration in a dedicated file and the business code in appropriate classes. Then you should be able to, for example, run a script without loading the web and the whole framework configuration. In this ideal scenario, the web framework just provides the glue between the user and your model.&lt;/p&gt;
&lt;h3 id=&#34;controllers&#34;&gt;Controllers&lt;/h3&gt;
&lt;p&gt;Routes are defined similarly between Django and Mojolicious. Usually you put the code in a class and then point to it, attaching a name to it so you can reference it elsewhere. The language is different, the style is different, but they essentially do the same thing.&lt;/p&gt;
&lt;p&gt;Django:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;django.urls&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;.&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; views
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;urlpatterns = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    path(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;api/agents/&amp;lt;int:agent_id&amp;gt;&amp;#34;&lt;/span&gt;, views.api_agent_view, name=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;api_agent_view&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The function &lt;code&gt;views.api_agent_view&lt;/code&gt; will receive the request with the &lt;code&gt;agent_id&lt;/code&gt; as a parameter.&lt;/p&gt;
&lt;p&gt;Mojolicious:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;startup&lt;/span&gt; ($self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# ....&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$r&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;routes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$r&lt;/span&gt;-&amp;gt;get(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/list/:sid&amp;#39;&lt;/span&gt;)-&amp;gt;to(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;API#list_texts&amp;#39;&lt;/span&gt;)-&amp;gt;name(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;api_list_texts&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;-&amp;gt;to&lt;/code&gt; method is routing the request to the &lt;code&gt;Myapp::Controller::API::list_texts&lt;/code&gt;, which will receive the request with the &lt;code&gt;sid&lt;/code&gt; as parameter.&lt;/p&gt;
&lt;p&gt;This is pretty much the core business of every web framework: routing a request to a given function.&lt;/p&gt;
&lt;p&gt;Mojolicious has also the ability to &lt;a href=&#34;https://docs.mojolicious.org/Mojolicious/Guides/Routing#Under&#34;&gt;chain the routes&lt;/a&gt; (pretty much taken from Catalyst). The typical use is authorization:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;startup&lt;/span&gt; ($self) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$r&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;routes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$api&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$r&lt;/span&gt;-&amp;gt;under(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/api/v1&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; ($c) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;req&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;headers&lt;/span&gt;-&amp;gt;header(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;X-API-Key&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#080&#34;&gt;eq&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;testkey&amp;#39;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;render(text =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Authentication required!&amp;#39;&lt;/span&gt;, status =&amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;401&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;undef&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$api&lt;/span&gt;-&amp;gt;get(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/check&amp;#39;&lt;/span&gt;)-&amp;gt;to(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;API#check&amp;#39;&lt;/span&gt;)-&amp;gt;name(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;api_check&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So the request to &lt;code&gt;/api/v1/check&lt;/code&gt; will first go in the first block and the chain will abort if the API key is not set in the header. Otherwise it will proceed to run the &lt;code&gt;API&lt;/code&gt; module&amp;rsquo;s &lt;code&gt;check&lt;/code&gt; function.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m Perl guy and so I&amp;rsquo;m a bit biased toward Mojolicious, but I also have a pragmatic approach to programming. Python is widely used — they teach it in schools — while Perl is seen as old-school, if not dead (like all the mature technologies). So, Python could potentially attract more developers to your project, and this is important to consider.&lt;/p&gt;
&lt;p&gt;Learning a new language like Python is not a big leap; it and Perl are quite similar despite the different syntax. I&amp;rsquo;d throw Ruby in the same basket.&lt;/p&gt;
&lt;p&gt;Of course both languages provide high quality modules you can use, and these two frameworks are an excellent example.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Perl Web Frameworks</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/04/perl-web-frameworks/"/>
      <id>https://www.endpointdev.com/blog/2022/04/perl-web-frameworks/</id>
      <published>2022-04-19T00:00:00+00:00</published>
      <author>
        <name>Marco Pessotto</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/04/perl-web-frameworks/nature-wing-black-and-white-web-line-insect-1244977.webp&#34; alt=&#34;Spider webs and spiders&#34;&gt;&lt;/p&gt;
&lt;!-- Photo: https://pxhere.com/en/photo/1244977 CC0 Public Domain --&gt;
&lt;h3 id=&#34;cgi&#34;&gt;CGI&lt;/h3&gt;
&lt;p&gt;When I started programming, back in the day, CGI (the Common Gateway Interface) was still widely
used. Usually the Apache webserver would just execute a script or a
binary with some environment variables set and serve whatever the
executable sent to the standard output, while keeping the standard
error in the logs.&lt;/p&gt;
&lt;p&gt;This simple and straightforward mechanism can still
be used for small programs, but larger applications usually want to
save the start-up time and live longer than just a single request.&lt;/p&gt;
&lt;p&gt;At that time Perl was used far more often than now, and it had (and still
has) the &lt;a href=&#34;https://metacpan.org/pod/CGI&#34;&gt;CGI.pm&lt;/a&gt; module to help the
programmer to get the job done.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;utf8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;CGI&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;CGI&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt;-&amp;gt;header;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Marco&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$q&lt;/span&gt;-&amp;gt;p(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Hello $name&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;print&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And it will output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./cgi.pl
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html; charset=ISO-8859-1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;p&amp;gt;Hello Marco&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here the script mixes logic and formatting and the encoding it
produces by default tells us that this comes from another age. But
if you want something which is seldom used and gets executed on demand
without persisting in the machine’s memory, this is still an option.&lt;/p&gt;
&lt;p&gt;Please note that there are frameworks which can work in CGI mode, so
there is no reason to use CGI.pm, beside having to maintain legacy
programs.&lt;/p&gt;
&lt;h3 id=&#34;mojolicious&#34;&gt;Mojolicious&lt;/h3&gt;
&lt;p&gt;Fast-forward to 2022.&lt;/p&gt;
&lt;p&gt;Nowadays Perl is just another language among dozens of them. But it
still gets the job done and lets you write nice, maintainable code like
any other modern language.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://mojolicious.org/&#34;&gt;Mojolicious&lt;/a&gt; is currently the top choice if
you want to do web development in Perl. It is an amazing framework,
with a large and active community, and appears to have collected the
best concepts that other web frameworks from other languages have to
offer.&lt;/p&gt;
&lt;p&gt;Let’s hack an app in a couple of minutes in a single file, like during
the CGI days:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;utf8&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojolicious::Lite&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;stash(name =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Marco&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;render(template =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;index&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;app&lt;/span&gt;-&amp;gt;start;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;__DATA__&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;@@&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;index&lt;/span&gt;.html.ep
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello &amp;lt;&lt;span style=&#34;color:#369&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here the structure is a bit different.&lt;/p&gt;
&lt;p&gt;First, there’s a Domain Specific Language (DSL) to give you some sugar.
This is the “Lite” version, while in a well-structured Mojolicious app one
prefers to write class methods. We declare that the root (&lt;code&gt;/&lt;/code&gt;) URL path of the
application is going to execute some code. It populates the “stash”
with some variables, and finally renders a template which can access
the stashed variables. If you execute the script, you get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl cgi 2&amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Status: 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Fri, 08 Apr 2022 12:33:52 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html;charset=UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The logging to the standard error stream is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92508] [163133] [debug] [82ae3iV2] GET &amp;#34;/&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92532] [163133] [debug] [82ae3iV2] Routing to a callback
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92565] [163133] [debug] [82ae3iV2] Rendering template &amp;#34;index.html.ep&amp;#34; from DATA section
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:33:52.92610] [163133] [debug] [82ae3iV2] 200 OK (0.001021s, 979.432/s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is basically what a modern framework is supposed to do.&lt;/p&gt;
&lt;p&gt;The nice thing in this example is that we created a single-file
prototype and launched it as a CGI. But we can also launch it as
daemon and visit the given address with a browser, which is how you
should normally deploy it, usually behind a reverse proxy like
&lt;a href=&#34;https://nginx.org/en/&#34;&gt;nginx&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl daemon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:42.01827] [163409] [info] Listening at &amp;#34;http://*:3000&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Web application available at http://127.0.0.1:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53687] [163409] [debug] [CwM6zoUQ] GET &amp;#34;/&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53715] [163409] [debug] [CwM6zoUQ] Routing to a callback
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53752] [163409] [debug] [CwM6zoUQ] Rendering template &amp;#34;index.html.ep&amp;#34; from DATA section
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[2022-04-08 14:48:48.53808] [163409] [debug] [CwM6zoUQ] 200 OK (0.001209s, 827.130/s)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you want you can even launch it with HTTPS as well (please note the
syntax to pass the certificates).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./mojo.pl daemon -l &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;https://[::]:8080?cert=./ssl/fullchain.pem&amp;amp;key=./ssl/privkey.pem&amp;#39;&lt;/span&gt; -m production&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For a small application listening on a high port this is already
enough and the whole deployment problem goes away.&lt;/p&gt;
&lt;p&gt;Speaking about deployment, Mojolicious has basically no dependencies
other than the core modules and comes with a lot of goodies, for example
a &lt;a href=&#34;https://docs.mojolicious.org/Mojo/UserAgent&#34;&gt;non blocking user-agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Recently a legacy application needed to make some API calls. To speed up
the process, we wanted to make the requests in parallel. And here’s
the gist of the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyApp::Async&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# ... more modules here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::UserAgent&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Promise&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# .... other methods here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;example&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$email&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;test@example.com&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$ua&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::UserAgent&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;foreach&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$list&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;get_lists) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$promise&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$ua&lt;/span&gt;-&amp;gt;post_p(&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;_url(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;/api/v2/endpoint/$list-&amp;gt;{code}&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                  json =&amp;gt; { email =&amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$email&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          -&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt; = &lt;span style=&#34;color:#038&#34;&gt;shift&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt; = &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt;-&amp;gt;result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt;-&amp;gt;code =~ &lt;span style=&#34;color:#080;background-color:#fff0ff&#34;&gt;m/^2/&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         &lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;-&amp;gt;_update_db(&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                         &lt;span style=&#34;color:#038&#34;&gt;die&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$tx&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;req&lt;/span&gt;-&amp;gt;url . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; &amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#369&#34;&gt;$res&lt;/span&gt;-&amp;gt;code;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                     }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                 });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#038&#34;&gt;push&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;@promises&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$promise&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Mojo::Promise&lt;/span&gt;-&amp;gt;all(&lt;span style=&#34;color:#369&#34;&gt;@promises&lt;/span&gt;)-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;then&lt;/span&gt;(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt; }, &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt; = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;})-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;wait&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So a bunch of requests are run in parallel and then synced before
returning. Does it remind you of JavaScript? Of course. A lot of
common paradigms taken from other languages and frameworks were
implemented here, and you can find the best of them in this nice
package.&lt;/p&gt;
&lt;p&gt;But the point here is that it doesn’t need dozens of new modules
installed or upgraded. It’s just a single module in pure Perl that you
can even install in your application tree. This is a huge advantage if
you’re dealing with a legacy application which uses an old Perl tree
and you want to play safe.&lt;/p&gt;
&lt;p&gt;So, if you’re starting from scratch, go with Mojolicious. It lets you
prototype fast and doesn’t let you down later.&lt;/p&gt;
&lt;p&gt;However, starting from scratch is not always an option. Actually, it’s
a rare opportunity. There’s a whole world of legacy applications and
they generate real money every day. It’s simply not possible or even
desirable to throw away something that works for something that would
do the same thing but in a “cooler” way. In ten years, the way we’re
coding will look old anyway.&lt;/p&gt;
&lt;h3 id=&#34;interchange&#34;&gt;Interchange&lt;/h3&gt;
&lt;p&gt;Wait. Isn’t &lt;a href=&#34;https://www.interchangecommerce.org&#34;&gt;Interchange&lt;/a&gt; an old
e-commerce framework? Yes, it’s not exactly a generic web framework,
on the contrary, it’s a specialized one, but it’s still a framework and
you can still do things in a maintainable fashion. The key is using
the so-called action maps:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ActionMap jump &amp;lt;&amp;lt;EOR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sub {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # get the path parameters
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my ($action, @args) = split(/\//, shift);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # get the query/body parameters
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    my $param = $CGI-&amp;gt;{param};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # redirect to another page
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ location =&amp;gt; $final });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or serve JSON
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ type =&amp;gt; &amp;#39;application/json&amp;#39;, body =&amp;gt; $json_string });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or serve a file
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;deliver({ type =&amp;gt; &amp;#39;text/plain&amp;#39;, body =&amp;gt; $bigfile });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    # or populate the &amp;#34;stash&amp;#34; and serve a template page
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $Tag-&amp;gt;tmp(stash_variable =&amp;gt; &amp;#34;Marco&amp;#34;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    $CGI-&amp;gt;{mv_nextpage} = &amp;#34;test.html&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EOR&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In &lt;code&gt;pages/test.html&lt;/code&gt; you would put this template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;Hello [scratch stash_variable]&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;p&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, I can’t show you a simple script which demonstrates this and
you’ll have to take my word for it since we can’t go through the
installation process here for a demo.&lt;/p&gt;
&lt;p&gt;Interchange is old, and it shows its
years, but it is actively maintained. It lacks many of Mojo’s
goodies, &lt;em&gt;but&lt;/em&gt; you can still do things in a reasonable way.&lt;/p&gt;
&lt;p&gt;In the example the code will execute
when a path starting with &lt;code&gt;/jump/&lt;/code&gt; is requested. The whole path is
passed to the routine, so you can split at &lt;code&gt;/&lt;/code&gt;, apply your logic, and
finally either set &lt;code&gt;$CGI-&amp;gt;{mv_nextpage}&lt;/code&gt; to a file in the &lt;code&gt;pages&lt;/code&gt;
directory or output the response body directly with &lt;code&gt;deliver&lt;/code&gt;. This
way you can easily build, as a classical example, an API.&lt;/p&gt;
&lt;p&gt;It’s a bit of a poor man’s
&lt;a href=&#34;https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller&#34;&gt;MVC&lt;/a&gt;
but it works. That’s basically the core of what a framework like
&lt;a href=&#34;https://metacpan.org/pod/Dancer2&#34;&gt;Dancer&lt;/a&gt; does.&lt;/p&gt;
&lt;h3 id=&#34;dancer-1--2&#34;&gt;Dancer (1 &amp;amp; 2)&lt;/h3&gt;
&lt;p&gt;Dancer is basically Ruby’s
&lt;a href=&#34;https://github.com/sinatra/sinatra&#34;&gt;Sinatra&lt;/a&gt; ported to Perl. As
already mentioned, ideas developed in other languages and frameworks
are often ported to Perl, and this is no exception.&lt;/p&gt;
&lt;p&gt;Let’s see it in an action:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;strict&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;warnings&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Dancer2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;/&amp;#39;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$name&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Marco&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Hello $name\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;start;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Start the script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Dancer2 v0.400000 server 22969 listening on http://0.0.0.0:3000&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Try it with &lt;code&gt;curl&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -D - http://0.0.0.0:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.0 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Date: Mon, 11 Apr 2022 07:22:18 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer2 0.400000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer2 0.400000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html; charset=UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If in the script you say &lt;code&gt;use Dancer;&lt;/code&gt; instead of &lt;code&gt;use Dancer2&lt;/code&gt;, you get:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl -D - http://0.0.0.0:3000
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HTTP/1.0 200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Server: Perl Dancer 1.3513
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Length: 12
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Content-Type: text/html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;X-Powered-By: Perl Dancer 1.3513
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello Marco&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Dancer’s core doesn’t do much more than routing. And you’ll also
notice that the syntax is very similar to Mojolicious::Lite. So to
get something done you need to start installing plugins which will
provide the needed glue to interact with a database, work with your
template system of choice, and more.&lt;/p&gt;
&lt;p&gt;Today you would wonder why you should use Dancer and not Mojolicious,
but when Dancer was at the peak of its popularity the games were still
open. There were plenty of plugins being written and published on CPAN.&lt;/p&gt;
&lt;p&gt;Around 2013 Dancer’s development team decided to rewrite it to make
it better. The problem was that plugins and templates needed to be
adapted as well. I’m under the impression that the energy got divided
and the momentum was lost. Now there are two codebases and two
plugin namespaces which do basically the same thing, because for
the end user there is not much difference.&lt;/p&gt;
&lt;h3 id=&#34;catalyst&#34;&gt;Catalyst&lt;/h3&gt;
&lt;p&gt;So what was attracting people to Dancer? When Dancer came out, Perl
had a great MVC framework, which is still around,
&lt;a href=&#34;http://catalyst.perl.org/&#34;&gt;Catalyst&lt;/a&gt;. (And note that the main
Mojolicious developer was on the Catalyst team.)&lt;/p&gt;
&lt;p&gt;Now, the problem is that to get started with Catalyst, even if it has
plenty of documentation, you need to be already acquainted with a lot
of concepts and technologies. For example, the
&lt;a href=&#34;https://metacpan.org/dist/Catalyst-Manual/view/lib/Catalyst/Manual/Tutorial/03_MoreCatalystBasics.pod&#34;&gt;tutorial&lt;/a&gt;
starts to talk about
&lt;a href=&#34;http://www.template-toolkit.org/&#34;&gt;Template Toolkit&lt;/a&gt; and
the &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class&#34;&gt;DBIx::Class&lt;/a&gt; ORM very early.&lt;/p&gt;
&lt;p&gt;These two modules are great and powerful and they deserve to be
studied, but for someone new to modern web development, or even to
Perl, it feels (and actually is) overwhelming.&lt;/p&gt;
&lt;p&gt;So, why would you choose Catalyst today? Catalyst has the stability
which Mojo, at least at the beginning, lacked, while
backward compatibility is a priority for Catalyst. The other way to look
at this is that Catalyst doesn’t see much current
&lt;a href=&#34;https://metacpan.org/dist/Catalyst-Runtime/changes&#34;&gt;development&lt;/a&gt;, but
someone could see this as a feature.&lt;/p&gt;
&lt;p&gt;Even if Catalyst predates all the hyper-modern features that Mojo has,
it’s still a modern framework, and a good one. I can’t show you a self
contained script (you need a tree of files), but I’d like to show you
what makes it very nice and powerful:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;MyApp::Controller::Root&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Moose&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;namespace::autoclean&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;BEGIN&lt;/span&gt; { extends &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Catalyst::Controller&amp;#39;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# start the chain with /foo/XX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;foo&lt;/span&gt; :Chained(&amp;#39;/&amp;#39;) CaptureArgs(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;stash(name =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;$arg&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# /foo/XX/bar/YY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;bar&lt;/span&gt; :Chained(&amp;#39;foo&amp;#39;) Args(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;detach(&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;view(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;JSON&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# /foo/XX/another/YY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;another&lt;/span&gt; :Chained(&amp;#39;foo&amp;#39;) Args(1) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; (&lt;span style=&#34;color:#369&#34;&gt;$self&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$arg&lt;/span&gt;) = &lt;span style=&#34;color:#369&#34;&gt;@_&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;detach(&lt;span style=&#34;color:#369&#34;&gt;$c&lt;/span&gt;-&amp;gt;view(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;HTML&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, if you hit &lt;code&gt;/foo/marco/bar/test&lt;/code&gt; the second path fragment will be
processed by the first method (&lt;code&gt;CaptureArgs(1)&lt;/code&gt;) and saved in the
stash. Then the second &lt;code&gt;bar&lt;/code&gt; method will be chained to it and the
&lt;code&gt;name&lt;/code&gt; will be available in the stash. The last method will be hit
with &lt;code&gt;/foo/marco/another/test2&lt;/code&gt;. (Incidentally, please note that
Mojolicious has
&lt;a href=&#34;https://docs.mojolicious.org/Mojolicious/Guides/Routing#Nested-routes&#34;&gt;nested&lt;/a&gt;
routes as well.)&lt;/p&gt;
&lt;p&gt;Now, I think it’s clear that in this way you can build deep hierarchies
of paths with reusable components. This works really great with the
DBIx::Class ORM, where you can
chain queries as well. As you can imagine, this is far from a simple
setup. On the contrary, this is an advanced setup for people who
already know their way around web frameworks.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;So, to sum up this excursion in the amazing land of Perl web
frameworks: If you build something from scratch, go with Mojolicious.
It’s your best bet. If nothing else, it’s super-easy to install, with
basically no dependencies.&lt;/p&gt;
&lt;p&gt;However, there’s no need to make a religion
out of it. Rewriting code without a clear gain is a waste of time and
money. A good developer should still be able to write maintainable
code with the existing tools.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
