Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Tree: 03d096c941
Fetching contributors…

Cannot retrieve contributors at this time

executable file 292 lines (256 sloc) 11 KB
# Tweetylicious! A one-file microblog application #
# this file is meant as an example of how easy it #
# is to create cool web applications using cutting #
# edge technology, with Perl 5, JavaScript and #
# just a few lines of code! #
# #
# Tweetylicious is meant as a hommage to Twitter, #
# a very cool micro-blogging site, but is in no #
# way affiliated with it. We hope this work, which #
# is released for free as open source software #
# (see LICENSE in the bottom), will stimulate #
# all the young minds out there to create even #
# more amazing stuff. Viva la revolution! :) #
# first we create our database (model) #
package Model;
use ORLite {
file => 'tweetylicious.db',
cleanup => 'VACUUM',
create => sub {
my $dbh = shift;
password TEXT NOT NULL,
email TEXT,
gravatar TEXT,
bio TEXT
# this validates registration data before we commit to the database
sub validate {
my ($user, $pass, $pass2) = @_;
return 'username field must not be blank' unless $user and length $user;
return 'password field must not be blank' unless $pass and length $pass;
return 'please re-type your password' unless $pass2 and length $pass2;
return "passwords don't match" unless $pass eq $pass2;
return 'sorry, this user already exists'
if Model::User->count( 'WHERE username = ?', $user) > 0;
# now the web application #
package main;
use Mojolicious::Lite;
use Mojo::ByteStream 'b'; # for unicode and md5
# this is a fake static route for our static data (static.js, static.css)
get '/static' => 'static';
# this controls the main index page
get '/' => 'index';
# these two control a user registering
get '/join' => 'join';
post '/join' => sub {
my $self = shift;
my $user = $self->param('username');
my $error = Model::validate( $user, $self->param('pwd'), $self->param('re-pwd'));
$self->stash( error => $error );
return if $error;
username => $user,
password => b(app->secret . $self->param('pwd'))->md5_sum,
email => $self->param('email'),
gravatar => b($self->param('email'))->md5_sum,
bio => $self->param('bio'),
# auto-login the user after he joins, and redirect to /
$self->session( name => $user );
} => 'join';
# let's rock and roll!
# finally, the templates #
@@ layouts/main.html.ep
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "">
<html xmlns="">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link type="text/css" href="" rel="Stylesheet" />
<link type="text/css" rel="stylesheet" media="screen" href="/static.css" rel="Stylesheet" />
<script type="text/javascript" src=""></script>
<script type="text/javascript" src=""></script>
<script type="text/javascript" src="/static.js"></script>
<div id="header"><a href="/"><div id="logo">Tweetylicious!</div></a>
<div class="options">
<a href="/login">Sign-In</a><a href="/join">Join us!</a>
<%= content %>
<div id="footer" class="ui-corner-all">Tweetylicious is Powered by <a href="">Perl 5</a>, <a href="">Mojolicious</a>, <a href="">ORLite</a> and <a href="">jQuery</a>! Released under <a href="">the same terms as Perl itself</a>. </div>
@@ join.html.ep
% layout 'main';
<div id="content" class="full ui-corner-all">
<h1>Join us, it's free!</h1>
% if (my $error = stash 'error') {
<div class="ui-state-error ui-corner-all" style="width:450px">
<span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em"></span><strong>Sorry:</strong> <%= $error %>
<hr />
% }
<form name="join" method="POST">
<tr><td>Username</td><td><input name="username" type="text" tabindex="1" value="<%= param 'username' %>" /></td></tr>
<tr><td>Password</td><td><input name="pwd" type="password" tabindex="2" value="<%= param 'pwd' %>" /></td></tr>
<tr><td>Password (again)</td><td><input name="re-pwd" type="password" tabindex="3" value="<%= param 're-pwd' %>" /></td></tr>
<tr><td>Email</td><td><input name="email" type="text" tabindex="4" value="<%= param 'email' %>" /></td></tr>
<span class="fineprint">Email is optional, and doesn't show in your page. It's used only to fetch your <a href="">gravatar</a></span>
<p>Tell us a bit about yourself - everyone will see it on your page</p>
<textarea class="ui-corner-all" tabindex="5" cols="50" rows="3" id="message" name="bio"><%= param 'bio' %></textarea>
<input type="submit" tabindex="6" value="Create!" />
@@ index.html.ep
% layout 'main';
<div id="content" class="info full ui-corner-all">
<h1>What is Tweetylicious?</h1>
<p>Tweetylicious is a <a href="">microblogging</a> web application in a single file! It was built from scratch using state of the art technology, and is meant to demonstrate how easy and fun it is to create your own Web applications in modern Perl 5!</p>
<h1>What are its features?</h1>
<li>Multi-user, with homepages, search and list of followers/following</li>
<li>Nice, clean, pretty interface (at least I think so :P)</li>
<li>User avatar images provided by <a href="">gravatar</a></li>
<li>Unicode support</li>
<li>Well structured, commented code, easy to expand and customize</li>
<li>Encrypted online sessions</li>
<li>Uses an actual database (SQLite) and stores encrypted user password</li>
<li>Free and Open Source Software, released under the same terms as Perl itself.</li>
<h1>How can you fit all that in a 'single file'?! It's gotta be huge and clobbered then!</h1>
<p>Not at all! Tweetylicious is built on top of Mojolicious::Lite and ORLite, two very simple modules that have absolutely no dependency other than Perl 5 itself. Mojolicious::Lite allows you to create powerful web applications in a very simple and clean fashion, while also letting you integrate your templates on the bottom of the file. ORLite is an extremely lightweight ORM for <a href="">SQLite</a> databases that lets you specify your schema on the fly.</p>
<p>Removing just blank lines and comments, the Model has ~80 lines, the Controller ~110 lines, templates ~170 lines, plus ~90 lines of static css and ~60 of static javascript. And that's the <strong>whole</strong> app.</p>
<p>But don't take my word for it, just browse through it :)</p>
<h1>What do I need to make it work on my own system?</h1>
<li>Perl 5 <span class="fineprint">(if you're running Linux or Mac, you already have it! Windows users can get it <a href="">here</a>)<span></li>
<li>SQLite 3</li>
<p>Tweetylicious also relies on the powerful jQuery JavaScript library, but that's downloaded and processed by the clients browser, so don't worry about it. Each user's avatar image is also provided externally, via gravatar.</p>
<p>Have fun!</p>
@@ static.css.ep
body {
margin:0 auto;
background: #0f1923; /* #333; */
a { text-decoration: none }
#header,#content,#sub-section,#footer {
#header li { display: inline }
#logo {
float: left;
background: #0972a5;
height: 60px;
font-family: "Georgia", "Times New Roman", serif;
font-size: 26px;
color: #eee;
padding: 50px 10px 10px 10px;
.options { text-align: right; margin-left: 450px; margin-top: -5px }
.search { float: right; margin-top: 50px }
#search { background-color: #bbb; color: #444; width: 200px; font-size: 16px; }
#content {
background: #efe;
font-family: "Verdana", sans-serif;
min-height: 100px;
padding-left: 10px;
h1 { font-size: 1.2em }
#title { margin-left: 30px; }
.half { width: 78.7% }
.full { width: 100% }
.fineprint { font-size: 0.6em }
ul { margin: 0; padding: 0; list-style: none; list-style-position: outside; }
.info { padding-bottom: 10px }
.info ul { list-style-type: square}
.info ul li{ margin-bottom:10px }
#content ul {
display: block;
width: 90%;
margin: 10px auto;
#content ul.messages li {
border-top: 1px solid #ddd;
padding-top: 16px;
height: 70px;
margin-top: 10px;
.when { display: block; font-size: 10px; color: #aaa; }
img { float: left; margin: 1px; border: 0 }
#content .ui-icon { float: right; position: relative; top: -10px; right: 10px }
#content a:hover.ui-icon { border: 1px #ff0 dashed }
#content a { text-decoration: none }
.who { margin-right: 8px; font-weight: bold }
#sub-section {
width: 20%;
background: #ccc;
font: 0.8em "Verdana", sans-serif;
#message {
border: 1px solid #aaa;
padding: 4px 2px;
resize: none;
font-size: 1.15em;
font-family: sans-serif;
color: #333;
#post {
margin: 10px 50px 30px 50px;
#post input { margin-right: 54px; float: right; font-size: 0.6em; }
#charsleft {
display: block;
float: left;
font-weight: bold;
.orange { color: #ff6300 }
.red { color: #d11 }
#bio li { margin: 6px; line-height: 1em; }
#bio span, #followers span, #following span, #totalposts span {
font-weight: bold;
margin-right: 4px;
#followers li, #following li { margin: 1px }
#followers, #following, #totalposts { clear: both; margin-left: 5px; padding-top: 10px }
/* safari and opera need this */
#header,#footer {width:100%}
#content,#sub-section {float:left; margin-top: 20px; min-height: 360px; }
#footer {clear:left; margin: 20px auto; padding-top: 10px;height: 26px; background: #555; color: #ccc; font-size:12px; text-align: center; }
#footer a { text-decoration: none; color: #eee }
@@ static.js.ep
$(function() {
// creating our buttons
Jump to Line
Something went wrong with that request. Please try again.