summaryrefslogtreecommitdiff
path: root/lib/TUWF/Intro.pod
blob: deb0236df432c1a8afd7301fac5620f40fa8c366 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
=encoding utf8

=head1 NAME

TUWF::Intro - A cookbook-style introduction to TUWF.

=head1 DESCRIPTION

The main L<TUWF> documentation already has a short introduction and is a good
reference, but it doesn't really tell you how to get started on a simple
website. This document lists a bunch of examples, starting from the basics,
that will show you the general principles and philosophy behind TUWF.

This is a documentation-only module, a C<use TUWF::Intro;> will not work.


=head1 THE BASICS

=head2 A single-file website

What sets TUWF apart from many other modern web frameworks is that it does not
assume a directory structure for your project. There is, by default, no "TUWF
configuration file" and no C<public/> directory where TUWF will serve files
from. A TUWF website is more like an old CGI script: You write a script, and
that script B<is> the website. Here is the script for a complete TUWF website:

  #!/usr/bin/perl

  use strict;
  use warnings;

  # Load the TUWF module. In this case we also import the 'Txt' function from
  # TUWF::XML, which allows us to easily output text.
  use TUWF 'Txt';

  # Register a request handler for the root path.
  TUWF::get '/' => sub {
    # Output some text
    Txt 'Hello, World.';
  };

  # And now 'run' the website. This should always be the last thing in the script.
  TUWF::run;

That's it. Save that to I<example.pl> and you have a website that will show an
I<Hello world> page. If you have L<HTTP::Server::Simple> installed, you can run
the script on the command line to start a local web server on port 3000. You
can also point your web server to the script to serve it through CGI or
FastCGI. See the L<Server configuration|TUWF/"SERVER CONFIGURATION">
documentation for such configuration examples.


=head2 Request handlers & request data

The previous example had a request handler for C</>, which means that the
subroutine (handler) is called whenever there is a GET request for the root
path. You can also register a handler for other HTTP methods:

  TUWF::post '/' => sub { .. };
  TUWF::put '/' => sub { .. };
  TUWF::any ['GET', 'HEAD', 'POST'], '/' => sub { .. };

And for different paths:

  TUWF::get '/about' => sub { .. };
  TUWF::post '/forum/create-thread' => sub { .. };

You can also use regular expressions. The sub-expressions are available through
C<< tuwf->capture() >>:

  TUWF::get qr{/user/(.+)} => sub {
    my $username = tuwf->capture(1);
  };

  # Or, using named captures:
  TUWF::get qr{/user/(?<username>.+)} => sub {
    my $username = tuwf->capture('username');
  };

The C<tuwf> function that you see above gives you access to the global I<TUWF
Object>. This object has many useful methods to get request data, generate a
response, access a database, and other utilities that come in handy. Here's a
few examples to get information from the request:

  TUWF::get '/' => sub {
    my $path = tuwf->reqPath;     # Returns '/' for this handler
    my $method = tuwf->reqMethod; # Returns 'GET'
    my $ip = tuwf->reqIP;         # Returns the users' IP address

    # Returns the value of the cookie named 'auth',
    # or undef if no such cookie was sent.
    my $auth = tuwf->reqCookie('auth');
  };

Other request methods can be found in L<TUWF::Request>.


=head2 Generating a response

A request handler typically generates a response to send back to the client.
There are a few different ways to do so. Let's start with the low-level tools
provided by L<TUWF::Response>; This example does the same as the I<Hello world>
example we started with:

  TUWF::get '/' => sub {
    # Set the HTTP response code.
    # This is a silly example, as '200' is the default.
    tuwf->resStatus(200);

    # Set a HTTP header.
    tuwf->resHeader('Cache-Control' => 'no-cache');

    # Write some text to the HTTP body.
    my $fd = tuwf->resFd;
    print $fd 'Hello, World.';
  };

C<resFd> gives you a UTF-8-enabled file handle where you can write a textual
HTTP body to. You typically don't want to use it, though. There are more
convenient alternatives available for different types of output. The
recommended approach of generating dynamic HTML and XML is to use L<TUWF::XML>,
which can be used directly:

  use TUWF ':Html5';

  # A convenience function that serves as our HTML template. Let's use
  # title-case function naming here for HTML-generating functions.
  sub Framework {
    my %options = @_;
    Html sub {
      Head sub {
        Title $options->{title};
      };
      Body sub {
        H1 $options->{title};
        Div id => 'main', $options->{body};
      };
    };
  };

  TUWF::get '/' => sub {
    Framework title => 'Main page', body => sub {
      Txt 'This is the body of the main page';
    };
  };

A note on naming conventions: The above example uses Title case for functions
that generate HTML. This is a convenient scheme to avoid naming clashes with
other functions and to make it clear what the function is doing, but you're not
forced to use this convention. L<TUWF::XML> can export HTML generation
functions with different naming conventions as well.

If the example seems overly magical to you, don't worry, the rules for
converting that DSL-like code into proper HTML are all explained in
L<TUWF::XML>. If you prefer something less magical, you can still use any
templating system of your choice. Here's an example with
L<HTML::Template::Pro>:

  use TUWF;
  use HTML::Template::Pro;

  TUWF::get '/' => sub {
    my $tpl = HTML::Template::Pro->new(filename => 'templates/main.tmpl');
    $tpl->param(title => 'Main page');
    $tpl->param(body => 'This is the body of the main page');
    tuwf->resBinary($tpl->output);
  };

See L</Extending TUWF> below for ways to fully integrate alternative templating
systems in TUWF.


=head2 Growing beyond a single file

It's surprising how much you can already do with a single-file website, but
some projects are too large keep cramming all functionality into a single file.
In those cases, you'll want to have a directory structure for your project and
ways to split up assets and functionality into multiple files. TUWF does not
enforce a directory structure, so we're free to think of something on our own.
Let's go with the following relatively standard structure:

  myproject/
  ├── bin/
  │   └── site.pl
  ├── lib/
  │   └── MyProject/
  │       ├── Homepage.pm
  │       └── Articles.pm
  └── public/
      ├── logo.svg
      ├── scripts.js
      └── style.css

Here, I<site.pl> would be our main TUWF script. It doesn't have to do much by
itself, it would only have to load the right code, and initialize TUWF. Here's
what it could look like:

  #!/usr/bin/perl

  use strict;
  use warnings;
  use FindBin '$Bin';
  use TUWF;

  # Make sure we can load modules from our 'lib' directory.
  use lib '$Bin/../lib';

  # Setup a 'before' hook that intercepts requests for static assets.
  TUWF::hook before => sub {
    tuwf->done if tuwf->resFile("$Bin/../public", tuwf->reqPath);
  };

  # Load our Perl modules
  require MyProject::Homepage;
  require MyProject::Articles;

  # And run!
  TUWF::run;

Now, all request handlers can go into the files in C<lib/>. Here's an example
C<lib/MyProject/Homepage.pm>:

  package MyProject::Homepage;

  use strict;
  use warnings;
  use TUWF 'Html5';

  TUWF::get '/' => sub {
    H1 sprintf 'Hello from %s!', __PACKAGE__;
  };

  1;

Maintaining the necessary C<require MyProject::..> lines for all modules in the
project may get tiring for large websites. TUWF comes with a convenient
function to recursively load all modules in a project:

  TUWF::set import_modules => 0; # Disable legacy import behavior.
  TUWF::load_recursive 'MyProject';



=head1 GOING FORWARD

There are many other possible topics to cover. This chapter lists a few
examples for slightly more advanced scenarios, but this document in by no means
complete. Check out the documentation for individual TUWF modules to learn more
about their functionality.


=head2 Extending TUWF

TUWF is a very minimal framework. It's more of a small set of tools than a
full-blown framework which holds all the answers for every scenario you'll
encounter. If you need anything that's not directly provided by TUWF, there's a
simple way to extend it: You can easily add methods and data to the main
C<tuwf> object.

For example, to simplify the earlier example where we used
L<HTML::Template::Pro> in a request handler, we might want to move the template
handling code in a separate method and make that method part of TUWF:

  use HTML::Template::Pro;

  sub TUWF::Object::template {
    my($tuwf, $template, %params) = @_;
    my $tpl = HTML::Template::Pro->new(filename => "templates/$template.tmpl");
    $tpl->param(%params);
    $tuwf->resBinary($tpl->output);
  };

This method can be defined in the main TUWF script or in any other project
file. It can be used from a request handler as follows:

  TUWF::get '/' => sub {
    tuwf->template('main',
      title => 'Main page',
      body => 'This is the body of the main page'
    );
  };


=head1 SEE ALSO

L<TUWF>, L<TUWF::DB>, L<TUWF::Misc>, L<TUWF::Request>, L<TUWF::Response>, L<TUWF::XML>.

The homepage of TUWF can be found at
L<https://dev.yorhel.nl/tuwf|https://dev.yorhel.nl/tuwf>.

=head1 COPYRIGHT

Copyright (c) 2008-2018 Yoran Heling.

This module is part of the TUWF framework and is free software available under
the liberal MIT license. See the COPYING file in the TUWF distribution for the
details.


=head1 AUTHOR

Yoran Heling <projects@yorhel.nl>

=cut