For organizations still operating Perl applications through traditional CGI scripts, moving to Mojolicious is an opportunity to modernize the application without abandoning the Perl ecosystem.

CGI helped define the early web, but the way applications are built, deployed, secured, and scaled has changed dramatically. For organizations still operating Perl applications through traditional CGI scripts, moving to Mojolicious is an opportunity to modernize the application without abandoning the Perl ecosystem. Mojolicious provides routing, controllers, templates, middleware, asynchronous capabilities, WebSocket support, testing tools, and modern deployment options—giving legacy applications a much stronger foundation for the years ahead.
CGI was revolutionary for its time.
The model was straightforward:
Browser
↓
Web Server
↓
CGI Script
↓
Process
↓
ResponseA request could execute a Perl script, generate HTML, and return the result.
For small applications, this simplicity was a major advantage.
But as applications grew, the process-per-request model introduced challenges around:
Startup overhead
Application structure
Connection management
Shared state
Testing
Routing
Middleware
Scalability
Modern deployment
A mature CGI application can eventually look like:
/cgi-bin/
├── login.pl
├── users.pl
├── orders.pl
├── report.pl
├── admin.pl
├── search.pl
└── export.plEach script may contain its own combination of:
Parameter parsing
Authentication
Database queries
Business logic
HTML generation
Error handling
Over time, the result can become difficult to change safely.
Mojolicious provides a structured application framework that allows these concerns to be separated.
A traditional CGI request might look like:
Request
↓
Web Server
↓
Start Perl Process
↓
Parse Parameters
↓
Run Logic
↓
Generate HTML
↓
Exit ProcessA Mojolicious application typically looks more like:
Request
↓
Mojolicious
↓
Middleware
↓
Router
↓
Controller
↓
Service / Model
↓
Template / JSON
↓
ResponseThis creates explicit application boundaries.
For example:
Application
│
├── Routes
├── Controllers
├── Services
├── Models
├── Templates
├── Middleware
└── TestsThe biggest improvement is not simply performance.
It is structure.
Migrating a CGI application is rarely a matter of copying Perl files into a new framework.
The difficult part is discovering what those scripts actually do.
A single CGI file may contain:
Request Parsing
↓
Authentication
↓
Validation
↓
Database Queries
↓
Business Rules
↓
HTML Generation
↓
ResponseA modern application should separate those responsibilities.
For example:
Request
↓
Controller
↓
Service
↓
Repository
↓
Databaseand:
Controller
↓
Template
↓
HTMLThis separation makes future changes much safer.
Before rewriting anything, create an inventory.
Identify:
CGI scripts
URLs
Forms
Database queries
Sessions
Cookies
Authentication
External services
Cron jobs
File uploads
Generated reports
Static assets
Environment configuration
A useful mapping looks like:
Legacy CGI
│
├── /login.cgi
├── /orders.cgi
├── /users.cgi
└── /reports.cgibecoming:
Mojolicious
│
├── /login
├── /orders
├── /users
└── /reportsBut do not assume every CGI script needs to become one controller.
The new architecture should be based on business capabilities, not the old file structure.
A maintainable Mojolicious application might look like:
my-app/
│
├── lib/
│ └── MyApp.pm
│
├── script/
│ └── my_app
│
├── lib/MyApp/
│ ├── Controller/
│ ├── Model/
│ ├── Service/
│ └── Plugin/
│
├── templates/
│ ├── layouts/
│ └── pages/
│
├── public/
│ ├── css/
│ ├── js/
│ └── images/
│
└── t/The exact structure can vary, but the responsibilities should remain clear.
Handle HTTP requests.
Contain business operations.
Handle persistence and data access.
Render presentation.
Provide reusable application infrastructure.
This creates a clean flow:
HTTP
↓
Controller
↓
Service
↓
Data Layer
↓
DatabaseCGI applications often determine behavior using:
Script names
Query parameters
Form values
A legacy URL might look like:
/cgi-bin/orders.pl?id=123
In Mojolicious, the route can become explicit:
$r->get('/orders/:id')->to('orders#show');
The controller then handles the request:
sub show ($self) {
my $id = $self->param('id');
my $order = $self->orders->find($id);
$self->render(
template => 'orders/show',
order => $order
);
}This is much easier to reason about than embedding routing logic inside a CGI script.
Routes become part of the application's architecture.
One of the biggest modernization opportunities is separating business logic from request handling.
A legacy script might contain:
Read Parameters
↓
Validate
↓
Calculate Price
↓
Update Database
↓
Send Email
↓
Print HTMLA better design is:
Controller
↓
Order Service
├── Validation
├── Pricing
├── Persistence
└── NotificationThe controller becomes intentionally thin.
For example:
sub create ($self) {
my $params = $self->req->json;
my $order = $self->order_service->create($params);
$self->render(
json => {
id => $order->id
}
);
}Now the business operation can be tested independently from HTTP.
This is one of the most valuable changes you can make during migration.
CGI applications often generate HTML directly:
print "<html>";
print "<h1>$title</h1>";
print "</html>";That approach becomes difficult to maintain.
Mojolicious provides template support that lets presentation move into dedicated files.
For example:
templates/
└── orders/
└── show.html.epThe controller provides the data:
$self->render(
order => $order
);The template handles presentation.
Controller
↓
Data
↓
Template
↓
HTMLThis makes visual changes significantly safer.
It also makes it easier to introduce:
Reusable layouts
Partials
Shared navigation
Consistent error pages
Modern frontend assets
Legacy CGI applications often contain configuration inside scripts:
Database Host
Username
Password
API Keys
File PathsThis is dangerous and difficult to manage across environments.
A modern architecture separates configuration from application code:
Environment
↓
Configuration
↓
ApplicationFor example:
Development
↓
Development Config
Staging
↓
Staging Config
Production
↓
Production ConfigSensitive values should be managed through appropriate secret-management systems rather than committed to source control.
This also makes deployments much more predictable.
Database access is another area where CGI and long-running applications behave differently.
A CGI process may establish a database connection for each request:
Request
↓
Start Process
↓
Connect Database
↓
Query
↓
DisconnectA modern application can manage resources more efficiently within a persistent application process.
The architecture becomes:
Application
↓
Data Access Layer
↓
DatabaseThe exact connection-management approach depends on the database and deployment architecture.
The important migration consideration is:
Do not carry inefficient CGI-era database patterns into the new application simply because the SQL still works.
Review:
Connection handling
Transactions
Prepared statements
Query performance
Error handling
Timeouts
Connection limits
This is a good opportunity to eliminate years of accumulated database technical debt.
Authentication logic is frequently scattered across CGI scripts.
For example:
login.cgi
users.cgi
orders.cgi
admin.cgimay each implement slightly different authentication checks.
That creates inconsistency.
A modern application should centralize authentication:
Request
↓
Authentication
↓
Authorization
↓
ControllerDistinguish between:
Who is the user?
What is the user allowed to do?
For example:
User
↓
Authenticated
↓
Role / Permission
↓
Allowed OperationThis makes security rules easier to audit and test.
Migration is an excellent opportunity to address security issues that may have accumulated over years.
Review:
Input validation
Output escaping
CSRF protection
Session security
Cookie attributes
Authentication
Authorization
SQL injection
File uploads
Command execution
Secret storage
Security headers
Legacy CGI applications may contain assumptions that were acceptable years ago but are no longer appropriate for internet-facing systems.
Do not simply reproduce the old behavior.
Ask:
Does this behavior still meet today's security expectations?
One of the motivations for moving away from CGI can be more efficient request handling.
Traditional CGI can involve process startup for each request:
Request
↓
Process Startup
↓
Load Application
↓
Execute
↓
ExitA persistent Mojolicious deployment can instead keep the application loaded:
Application Process
│
┌────┼────┬────┐
▼ ▼ ▼ ▼
Req Req Req ReqThis can reduce repeated startup work.
But performance should be measured rather than assumed.
Benchmark:
Request latency
Throughput
Memory usage
Database latency
CPU utilization
Concurrent requests
The biggest gains may come from architectural improvements rather than the framework itself.
CGI applications often have limited automated testing.
Migration is an opportunity to build a stronger test strategy.
A useful model is:
Unit Tests
↓
Service Tests
↓
Controller Tests
↓
Integration Tests
↓
End-to-End TestsTest critical behavior such as:
Login
Order creation
Payments
Data updates
Permissions
Reports
File uploads
Do not attempt to recreate every historical implementation detail.
Test the behavior that the business actually depends on.
CGI deployments may have historically involved:
Copy Files
↓
Update Web Server
↓
Restart / ReloadA modern application can adopt:
Git
↓
Build
↓
Test
↓
Security Checks
↓
Package
↓
Deploy
↓
Health CheckThis makes deployments repeatable.
Modern Mojolicious applications can also fit into infrastructure such as:
Containers
Reverse proxies
Load balancers
Cloud platforms
CI/CD pipelines
Process managers
The goal is not to modernize infrastructure simply because it is fashionable.
The goal is to make deployment:
Repeatable
Observable
Recoverable
Automated
Large CGI applications can contain hidden business rules.
A big-bang rewrite can lose functionality that nobody documented.
Moving a 2,000-line CGI script into a controller does not create a modern application.
Not every historical behavior deserves permanent preservation.
Distinguish business requirements from accidental behavior.
A framework migration and a database migration can become two large projects.
Separate them unless there is a strong reason to combine them.
Existing URLs may be embedded in:
Bookmarks
Search engines
Emails
External integrations
Plan redirects and compatibility carefully.
CGI applications often have supporting cron jobs that are just as important as the web scripts.
Inventory them before migration.
Framework features help.
Correct security architecture matters more.
You need to know whether the new application is actually behaving better.
Monitor:
Errors
Latency
Traffic
Database performance
Memory
Resource utilization
Document:
CGI scripts
URLs
Dependencies
Databases
Authentication
Cron jobs
External services
Configuration
Identify:
Critical
Important
Legacy / Low ValueFocus first on business-critical functionality.
Create:
Application
Configuration
Routing
Logging
Error handling
Testing
Deployment
Move logic out of CGI scripts into reusable services.
CGI
↓
Business Logic
↓
ServiceThis allows the old and new interfaces to potentially coexist temporarily.
Map legacy URLs to new Mojolicious routes.
Use redirects where appropriate.
Move HTML generation into templates.
Introduce:
Layouts
Partials
Consistent components
Centralize security behavior.
Move database operations behind a clean data-access boundary.
For larger applications, a gradual transition can look like:
Users
│
▼
Reverse Proxy
/ \
▼ ▼
Legacy CGI Mojolicious
│ │
└─────┬─────┘
▼
DatabaseThis lets the team move functionality incrementally.
Once traffic has moved:
CGI
██████████
Mojolicious
██
↓
CGI
███
Mojolicious
███████
↓
CGI
█
Mojolicious
██████████Remove obsolete infrastructure only after the new implementation is proven.
Not every CGI application needs immediate migration.
CGI can still be reasonable for:
Tiny internal utilities
Rarely used administrative tools
Extremely stable legacy systems
Applications with minimal maintenance requirements
If the application is small, isolated, secure, and inexpensive to operate, a migration may provide little immediate business value.
Modernization becomes much more compelling when the application needs:
Frequent feature development
Higher traffic
Better testing
Modern deployment
API support
Improved security
Better maintainability
Horizontal scaling
Before starting a CGI-to-Mojolicious migration, ask:
How many business-critical workflows are hidden inside the existing CGI scripts?
Which URLs must remain compatible?
Which parts of the application can be migrated independently?
How much business logic is mixed with HTML and request handling?
Which dependencies are obsolete?
How will authentication and sessions work in the new architecture?
Can old and new implementations run side by side?
What performance and reliability improvements do we expect?Most importantly:
Are we modernizing the application—or simply moving the same legacy design into a newer framework?
That distinction determines whether the migration actually creates long-term value.
Migrating from CGI to Mojolicious is an opportunity to modernize more than the request handler.
The journey should look like:
Legacy CGI
↓
Inventory
↓
Behavior Mapping
↓
Modern Application Foundation
↓
Business Logic Separation
↓
Modern Routing
↓
Templates + APIs
↓
Security + Testing
↓
Modern Deployment
↓
Retire CGIDo not begin by rewriting every script.
Start by understanding the system.
Identify the business capabilities hidden inside the legacy code.
Separate business logic from HTTP handling.
Move presentation into templates.
Centralize authentication.
Modernize configuration.
Review database access.
Build automated tests around important workflows.
Run old and new implementations side by side when the application is large enough to justify an incremental migration.
And measure the results.
The goal is not to make old CGI scripts run inside a newer Perl framework. The goal is to transform a fragile collection of request-driven scripts into a maintainable application with clear boundaries, modern security, automated testing, predictable deployment, and room to evolve.
Mojolicious provides the foundation.
Good architecture provides the long-term value.
Modernize incrementally, preserve what the business actually needs, remove what no longer serves a purpose, and use the migration to create an application your team can confidently maintain for the next decade—not simply one that looks newer than the system you started with.
We build custom software, mobile apps, and web platforms for startups and enterprises.



Their team became an extension of ours — within months they'd rebuilt our entire product experience from the ground up.
