I think you might be following a really outdated CGI tutorial. Here is an example using the much more modern Mojolicious and Mojo::SQLite. It avoids many of the issues mentioned in this thread, but note it is not complete yet, for example there is no checking of duplicate submissions or rate limiting (someone could fill up your hard drive with lots of requests), no constraints on the database table (like unique nicknames), and no checks on the format of the submitted data, like no strict checking of URLs (so somebody could submit an image with nefarious content, or link to a nefarious website, for example). This particular Mojo app works as a CGI script as well, in addition to the usual Mojo ways like development via morbo. I hope this, along with Mojolicious::Guides::Tutorial, perlintro, and perlreftut, will be a much better starting point for you. The script does contain some advanced techniques, like my use of map to work with the columns, but if you have any questions, please feel free to ask!
use Mojolicious::Lite -signatures; # turns on strict and warnings too
use Mojo::SQLite;
use Hash::Ordered;
helper sqlite =>
sub { state $sql = Mojo::SQLite->new('sqlite:/path/to/mydb.db') };
# disable template cache when running under morbo/development mode
app->renderer->cache->max_keys(0) if app->mode eq 'development';
# define the database columns; used everywhere in this script
# and most code depends on this hash being ordered
tie my %COLS, 'Hash::Ordered',
nick=>'Nickname', pic=>'Picture', say=>'Says', likes=>'Likes',
fav=>'Favorite vehicle', car=>'Real life car/vehicle', age=>'Age',
town=>'Hometown', drink=>'Favorite drink', wpage=>'Webpage';
# dynamically generate the table definition based on the columns
# (if columns are modified later this might mess things up)
my $CREATE = app->sqlite->abstract->generate('create table',
'drivers', [ 'id INTEGER PRIMARY KEY AUTOINCREMENT',
map {"$_ TEXT"} sort keys %COLS ] );
app->sqlite->migrations->from_string(<<"END_MIGR")->migrate;
-- 1 up
$CREATE;
-- 1 down
DROP TABLE IF EXISTS drivers;
END_MIGR
# a normal GET request queries the database and renders the template
get '/' => sub ($c) {
$c->stash( COLS => \%COLS, rows =>
$c->sqlite->db->select('drivers', [keys %COLS])->hashes );
$c->render('main');
} => 'drivers';
# a POST request adds a row to the database and redirects back
post '/' => sub ($c) {
$c->sqlite->db->insert('drivers',
{ map { $_=>$c->param($_) } keys %COLS } );
$c->redirect_to($c->url_for('drivers'));
} => 'newdriver';
app->start;
__DATA__
@@ main.html.ep
<!DOCTYPE html>
<html>
<head><title>Drivers</title></head>
<body>
<h1>Drivers</h1>
% if (@$rows) {
<div><table border="1">
<tr>
% for my $col (values %$COLS) {
<th><%= $col %></th>
% }
</tr>
% for my $row (@$rows) {
<tr>
% for my $col (keys %$COLS) {
<td>
% if ($col eq 'pic') {
%= image $row->{$col}
% } elsif ($col eq 'wpage') {
%= link_to $row->{$col} => $row->{$col}
% } else {
%= $row->{$col}
% }
</td>
% }
</tr>
% }
</table></div>
% } else {
<div><b>No records found!</b></div>
% }
<h2>Add a New Driver</h2>
<div>
%= form_for newdriver => ( method => 'post' ) => begin
<table>
% for my $col (keys %$COLS) {
<tr>
<td><%= label_for $col => $COLS->{$col} %></td>
<td>
% if ($col eq 'pic' || $col eq 'wpage') {
%= url_field $col, placeholder=>'https://example.com/...',
required=>'required', pattern=>'https://.*'
% } else {
%= text_field $col, placeholder=>$COLS->{$col},
required=>'required'
% }
</td>
</tr>
% }
</table>
%= submit_button 'Add Driver'
%= end
</div>
</body>
</html>