# ABSTRACT: A gentle introduction to Dancer2 package Dancer2::Manual; __END__ =pod =encoding UTF-8 =head1 NAME Dancer2::Manual - A gentle introduction to Dancer2 =head1 VERSION version 1.1.0 =head1 DESCRIPTION Dancer2 is a free and open source web application framework written in Perl. It's a complete rewrite of L, based on L and using a more robust and extensible fully-OO design. It's designed to be powerful and flexible, but also easy to use - getting up and running with your web app is trivial, and an ecosystem of adaptors for common template engines, session storage, logging methods, serializers, and plugins to make common tasks easy means you can do what you want to do, your way, easily. =head1 INSTALL Installation of Dancer2 is simple, using your favourite method to install from CPAN, e.g.: perl -MCPAN -e 'install Dancer2' Thanks to the magic of cpanminus, if you do not have CPAN.pm configured, or just want a quickfire way to get running, the following should work, at least on Unix-like systems: wget -O - http://cpanmin.us | sudo perl - Dancer2 (If you don't have root access, omit the 'sudo', and cpanminus will install Dancer2 and prereqs into C<~/perl5>.) Dancer2 is also available as a package from the package repository of several distributions, for example on Debian/Ubuntu you should be able to just: apt-get install libdancer2-perl Do be aware, though, that distribution-packaged versions sometimes lag behind the most recent version on CPAN. =head1 BOOTSTRAPPING A NEW APP Create a web application using the dancer script: $ dancer2 gen -a MyApp && cd MyApp + MyApp + MyApp/config.yml + MyApp/Makefile.PL + MyApp/MANIFEST.SKIP + MyApp/.dancer + MyApp/cpanfile + MyApp/bin + MyApp/bin/app.psgi + MyApp/environments + MyApp/environments/development.yml + MyApp/environments/production.yml + MyApp/lib + MyApp/lib/MyApp.pm + MyApp/public + MyApp/public/favicon.ico + MyApp/public/500.html + MyApp/public/dispatch.cgi + MyApp/public/404.html + MyApp/public/dispatch.fcgi + MyApp/public/css + MyApp/public/css/error.css + MyApp/public/css/style.css + MyApp/public/images + MyApp/public/images/perldancer.jpg + MyApp/public/images/perldancer-bg.jpg + MyApp/public/javascripts + MyApp/public/javascripts/jquery.js + MyApp/t + MyApp/t/001_base.t + MyApp/t/002_index_route.t + MyApp/views + MyApp/views/index.tt + MyApp/views/layouts + MyApp/views/layouts/main.tt It creates a directory named after the name of the app, along with a configuration file, a views directory (where your templates and layouts will live), an environments directory (where environment-specific settings live), a module containing the actual guts of your application, and a script to start it. Finally, F<.dancer> indicates the root directory of your app, making it easier for Dancer2 to determine the various paths it needs for finding resources and code within your application. A default skeleton is used to bootstrap the new application, but you can use the C<-s> option to provide another skeleton. For example: $ dancer2 gen -a MyApp -s ~/mydancerskel For an example of a skeleton directory check the default one available in the C directory of your Dancer2 distribution. (In what follows we will refer to the directory in which you have created your Dancer2 application -- I what C was above -- as the C.) Because Dancer2 is a L web application framework, you can use the C tool (provided by L) for launching the application: plackup -p 5000 bin/app.psgi View the web application at: http://localhost:5000 =head1 USAGE When Dancer2 is imported to a script, that script becomes a webapp, and at this point, all the script has to do is declare a list of B. A route handler is composed by an HTTP method, a path pattern and a code block. C, C and C pragmas are also imported with Dancer2. The code block given to the route handler has to return a string which will be used as the content to render to the client. Routes are defined for a given HTTP method. For each method supported, a keyword is exported by the module. =head2 HTTP Methods Here are some of the standard HTTP methods which you can use to define your route handlers. =over 4 =item * B The GET method retrieves information, and is the most common GET requests should be used for typical "fetch" requests - retrieving information. They should not be used for requests which change data on the server or have other effects. When defining a route handler for the GET method, Dancer2 automatically defines a route handler for the HEAD method (in order to honour HEAD requests for each of your GET route handlers). To define a GET action, use the L keyword. =item * B The POST method is used to create a resource on the server. To define a POST action, use the L keyword. =item * B The PUT method is used to replace an existing resource. To define a PUT action, use the L keyword. a PUT request should replace the existing resource with that specified - for instance - if you wanted to just update an email address for a user, you'd have to specify all attributes of the user again; to make a partial update, a PATCH request is used. =item * B The PATCH method updates some attributes of an existing resource. To define a PATCH action, use the L keyword. =item * B The DELETE method requests that the origin server delete the resource identified by the Request-URI. To define a DELETE action, use the L keyword. =back =head3 Handling multiple HTTP request methods Routes can use C to match all, or a specified list of HTTP methods. The following will match any HTTP request to the path C: any '/myaction' => sub { # code } The following will match GET or POST requests to C: any ['get', 'post'] => '/myaction' => sub { # code }; For convenience, any route which matches GET requests will also match HEAD requests. =head2 Route Handlers The route action is the code reference declared. It can access parameters through the specific L, L, and L keywords, which return a L object. This hashref is a merge of the route pattern matches and the request params. You can find more details about how params are built and how to access them in the L documentation. =head3 Declaring Routes To control what happens when a web request is received by your webapp, you'll need to declare C. A route declaration indicates which HTTP method(s) it is valid for, the path it matches (e.g. C), and a coderef to execute, which returns the response. get '/hello/:name' => sub { return "Hi there " . route_parameters->get('name'); }; The above route specifies that, for GET requests to C, the code block provided should be executed. You can also provide routes with a name: get 'hi_to' => '/hello/:name' => sub {...}; See L on how this can be used. =head3 Retrieving request parameters The L, L, and L keywords provide a L result from the three different parameters. =head3 Named matching A route pattern can contain one or more tokens (a word prefixed with ':'). Each token found in a route pattern is used as a named-pattern match. Any match will be set in the route parameters. get '/hello/:name' => sub { return "Hey " . route_parameters->get('name') . ", welcome here!"; }; Tokens can be optional, for example: get '/hello/:name?' => sub { my $name = route_parameters->get('name') // 'Whoever you are'; return "Hello there, $name"; }; =head3 Named matching with type constraints Type constraints can be added to tokens. get '/user/:id[Int]' => sub { # matches /user/34 but not /user/jamesdean my $user_id = route_parameters->get('id'); }; get '/user/:username[Str]' => sub { # matches /user/jamesdean but not /user/34 since that is caught # by previous route my $username = route_parameters->get('username'); }; You can even use type constraints to add a regexp check: get '/book/:date[StrMatch[qr{\d\d\d\d-\d\d-\d\d}]]' => sub { # matches /book/2014-02-04 my $date = route_parameters->get('date'); }; The default type library is L but any type library built using L's L can be used instead. If you'd like to use a different default type library you must declare it in the configuration file, for example: type_library: My::Type::Library Alternatively you can specify the type library in which the type is defined as part of the route definition: get '/user/:username[My::Type::Library::Username]' => sub { my $username = route_parameters->get('username'); }; This will load C and from it use the type C. This allows types to be used that are not part of the type library defined by config's C. More complex constructs are allowed such as: get '/some/:thing[Int|MyDate]' => sub { ...; }; See L for more details. =head3 Wildcard Matching A route can contain a wildcard (represented by a C<*>). Each wildcard match will be placed in a list, which the C keyword returns. get '/download/*.*' => sub { my ($file, $ext) = splat; # do something with $file.$ext here }; An extensive, greedier wildcard represented by C<**> (A.K.A. "megasplat") can be used to define a route. The additional path is broken down and returned as an arrayref: get '/entry/*/tags/**' => sub { my ( $entry_id, $tags ) = splat; my @tags = @{$tags}; }; The C keyword in the above example for the route F would set C<$entry_id> to C<1> and C<$tags> to C<['one', 'two']>. =head3 Mixed named and wildcard matching A route can combine named (token) matching and wildcard matching. This is useful when chaining actions: get '/team/:team/**' => sub { var team => route_parameters->get('team'); pass; }; prefix '/team/:team'; get '/player/*' => sub { my ($player) = splat; # etc... }; get '/score' => sub { return score_for( vars->{'team'} ); }; =head3 Regular Expression Matching A route can be defined with a Perl regular expression. In order to tell Dancer2 to consider the route as a real regexp, the route must be defined explicitly with C, like the following: get qr{/hello/([\w]+)} => sub { my ($name) = splat; return "Hello $name"; }; A route regex may use named capture groups. The C keyword will return a reference to a copy of C<%+>. =head3 Conditional Matching Routes may include some matching conditions (on content_type, agent, user_agent, content_length and path_info): get '/foo', {agent => 'Songbird (\d\.\d)[\d\/]*?'} => sub { 'foo method for songbird' } get '/foo' => sub { 'all browsers except songbird' } =head2 Prefix A prefix can be defined for each route handler, like this: prefix '/home'; From here, any route handler is defined to /home/* get '/page1' => sub {}; # will match '/home/page1' You can unset the prefix value prefix '/'; # or: prefix undef; get '/page1' => sub {}; # will match /page1 Alternatively, to prevent you from ever forgetting to undef the prefix, you can use lexical prefix like this: prefix '/home' => sub { get '/page1' => sub {}; # will match '/home/page1' }; ## prefix reset to previous value on exit get '/page1' => sub {}; # will match /page1 =head2 Delayed responses (Async/Streaming) L can provide delayed (otherwise known as I) responses using the C keyword. These responses are streamed, although you can set the content all at once, if you prefer. get '/status' => sub { delayed { response_header 'X-Foo' => 'Bar'; # flush headers (in case of streaming) flush; # send content to the user content 'Hello, world!'; # you can write more content # all streaming content 'Hello, again!'; # when done, close the connection done; # do whatever you want else, asynchronously # the user socket closed by now ... }; }; If you are streaming (calling C several times), you must call C first. If you're sending only once, you don't need to call C. Here is an example of using delayed responses with L: use Dancer2; use AnyEvent; my %timers; my $count = 5; get '/drums' => sub { delayed { print "Stretching...\n"; flush; # necessary, since we're streaming $timers{'Snare'} = AE::timer 1, 1, delayed { $timers{'HiHat'} ||= AE::timer 0, 0.5, delayed { content "Tss...\n"; }; content "Bap!\n"; if ( $count-- == 0 ) { %timers = (); content "Tugu tugu tugu dum!\n"; done; print "\n\n"; $timers{'Applause'} = AE::timer 3, 0, sub { # the DSL will not available here # because we didn't call the "delayed" keyword print "\n"; }; } }; }; }; If an error happens during a write operation, a warning will be issued to the logger. You can handle the error yourself by providing an C handler: get '/' => sub { delayed { flush; content "works"; # ... user disconnected here ... content "fails"; # ... error triggered ... done; # doesn't even get run } on_error => sub { # delayed{} not needed, DSL already available my ($error) = @_; # do something with $error }; }; Here is an example that asynchronously streams the contents of a CSV file: use Dancer2; use Text::CSV_XS qw< csv >; use Path::Tiny qw< path >; use JSON::MaybeXS qw< encode_json >; # Create CSV parser my $csv = Text::CSV_XS->new({ binary => 1, auto_diag => 1, }); get '/' => sub { # delayed response: delayed { # streaming content flush; # Read each row and stream it in JSON my $fh = path('filename.csv')->openr_utf8; while ( my $row = $csv->getline($fh) ) { content encode_json $row; } # close user connection done; } on_error => sub { my ($error) = @_; warning 'Failed to stream to user: ' . request->remote_address; }; }; B If you just want to send a file's contents asynchronously, use C instead of C, as it will automatically take advantage of any asynchronous capability. =head2 Action Skipping An action can choose not to serve the current request and ask Dancer2 to process the request with the next matching route. This is done with the B keyword, like in the following example get '/say/:word' => sub { pass if route_parameters->get('word') =~ /^\d+$/; "I say a word: " . route_parameters->get('word'); }; get '/say/:number' => sub { "I say a number: " . route_parameters->get('number'); }; =head1 HOOKS Hooks are code references (or anonymous subroutines) that are triggered at specific moments during the resolution of a request. They are set up using the L keyword. Many of them are provided by Dancer2's core, but plugins and engines can also define their own. =over 4 =item * C hooks C hooks are evaluated before each request within the context of the request and receives as argument the app (a L object). It's possible to define variables which will be accessible in the action blocks with the L. hook before => sub { var note => 'Hi there'; }; get '/foo/*' => sub { my ($match) = splat; # 'oversee'; vars->{note}; # 'Hi there' }; For another example, this can be used along with session support to easily give non-logged-in users a login page: hook before => sub { if (!session('user') && request->path !~ m{^/login}) { # Pass the original path requested along to the handler: forward '/login', { requested_path => request->path }; } }; The request keyword returns the current L object representing the incoming request. =item * C hooks C hooks are evaluated after the response has been built by a route handler, and can alter the response itself, just before it's sent to the client. This hook runs after a request has been processed, but before the response is sent. It receives a L object, which it can modify if it needs to make changes to the response which is about to be sent. The hook can use other keywords in order to do whatever it wants. hook after => sub { response->content( q{The "after" hook can alter the response's content here!} ); }; =back =head2 Templates =over 4 =item * C C hooks are called whenever a template is going to be processed, they are passed the tokens hash which they can alter. hook before_template_render => sub { my $tokens = shift; $tokens->{foo} = 'bar'; }; The tokens hash will then be passed to the template with all the modifications performed by the hook. This is a good way to setup some global vars you like to have in all your templates, like the name of the user logged in or a section name. =item * C C hooks are called after the view has been rendered. They receive as their first argument the reference to the content that has been produced. This can be used to post-process the content rendered by the template engine. hook after_template_render => sub { my $ref_content = shift; my $content = ${$ref_content}; # do something with $content ${$ref_content} = $content; }; =item * C C hooks are called whenever the layout is going to be applied to the current content. The arguments received by the hook are the current tokens hashref and a reference to the current content. hook before_layout_render => sub { my ($tokens, $ref_content) = @_; $tokens->{new_stuff} = 42; $ref_content = \"new content"; }; =item * C C hooks are called once the complete content of the view has been produced, after the layout has been applied to the content. The argument received by the hook is a reference to the complete content string. hook after_layout_render => sub { my $ref_content = shift; # do something with ${ $ref_content }, which reflects directly # in the caller }; =back =head2 Error Handling Refer to L for details about the following hooks: =over 4 =item * C =item * C =item * C =item * C =back =head2 File Rendering Refer to L for details on the following hooks: =over 4 =item * C =item * C =back =head2 Serializers =over 4 =item * C is called before serializing the content, and receives the content to serialize as an argument. hook before_serializer => sub { my $content = shift; ... }; =item * C is called after the payload has been serialized, and receives the serialized content as an argument. hook after_serializer => sub { my $serialized_content = shift; ... }; =back =head1 HANDLERS =head2 File Handler Whenever a content is produced out of the parsing of a static file, the L component is used. This component provides two hooks, C and C. C hooks are called just before starting to parse the file, the hook receives as its first argument the file path that is going to be processed. hook before_file_render => sub { my $path = shift; }; C hooks are called after the file has been parsed and the response content produced. It receives the response object (L) produced. hook after_file_render => sub { my $response = shift; }; =head2 Auto page Whenever a page that matches an existing template needs to be served, the L component is used. =head2 Writing your own A route handler is a class that consumes the L role. The class must implement a set of methods: C, C and C which will be used to declare the route. Let's look at L for example. First, the matching methods are C and C: sub methods { qw(head get) } Then, the C or the I we want to match: sub regexp { '/:page' } Anything will be matched by this route, since we want to check if there's a view named with the value of the C token. If not, the route needs to C, letting the dispatching flow to proceed further. sub code { sub { my $app = shift; my $prefix = shift; my $template = $app->template_engine; if ( !defined $template ) { $app->response->has_passed(1); return; } my $page = $app->request->path; my $layout_dir = $template->layout_dir; if ( $page =~ m{^/\Q$layout_dir\E/} ) { $app->response->has_passed(1); return; } # remove leading '/', ensuring paths relative to the view $page =~ s{^/}{}; my $view_path = $template->view_pathname($page); if ( ! $template->pathname_exists( $view_path ) ) { $app->response->has_passed(1); return; } my $ct = $template->process( $page ); return ( $app->request->method eq 'GET' ) ? $ct : ''; }; } The C method passed the L object which provides access to anything needed to process the request. A C is then implemented to add the route to the registry and if the C is off, it does nothing. sub register { my ($self, $app) = @_; return unless $app->config->{auto_page}; $app->add_route( method => $_, regexp => $self->regexp, code => $self->code, ) for $self->methods; } The config parser looks for a C section and any handler defined there is loaded. Thus, any random handler can be added to your app. For example, the default config file for any Dancer2 application is as follows: route_handlers: File: public_dir: /path/to/public AutoPage: 1 =head1 ERRORS =head2 Error Pages When an HTTP error occurs (i.e. the action responds with a status code other than 200), this is how Dancer2 determines what page to display. =over =item * Looks in the C directory for a corresponding template file matching the error code (e.g. C<500.tt> or C<404.tt>). If such a file exists, it's used to report the error. =item * Next, looks in the C directory for a corresponding HTML file matching the error code (e.g. C<500.html> or C<404.html>). If such a file exists, it's used to report the error. (Note, however, that if B is set to true, in the case of a 500 error the static HTML page will not be shown, but will be replaced with a default error page containing more informative diagnostics. For more information see L.) (In older versions, B was used instead of B. Both are supported, but B is deprecated.) =item * As default, render a generic error page on the fly. =back =head2 Execution Errors When an error occurs during the route execution, Dancer2 will render an error page with the HTTP status code 500. It's possible either to display the content of the error message or to hide it with a generic error page. This is a choice left to the end-user and can be controlled with the B setting (see above). =head2 Error Hooks When an error is caught by Dancer2's core, an exception object is built (of the class L). This class provides a hook to let the user alter the error workflow if needed. C hooks are called whenever an error object is built, the object is passed to the hook. hook init_error => sub { my $error = shift; # do something with $error }; I in Dancer, both names currently are synonyms for backward-compatibility.> C hooks are called whenever an error is going to be thrown, it receives the error object as its sole argument. hook before_error => sub { my $error = shift; # do something with $error }; I in Dancer, both names currently are synonyms for backward-compatibility.> C hooks are called whenever an error object has been thrown, it receives a L object as its sole argument. hook after_error => sub { my $response = shift; }; I in Dancer, both names currently are synonyms for backward-compatibility.> C is called when an exception has been caught, at the route level, just before rethrowing it higher. This hook receives a L and the error as arguments. hook on_route_exception => sub { my ($app, $error) = @_; }; =head1 SESSIONS =head2 Handling sessions It's common to want to use sessions to give your web applications state; for instance, allowing a user to log in, creating a session, and checking that session on subsequent requests. By default Dancer 2 has L sessions enabled. It implements a very simple in-memory session storage. This will be fast and useful for testing, but such sessions will not persist between restarts of your app. If you'd like to use a different session engine you must declare it in the configuration file. For example to use YAML file base sessions you need to add the following to your F: session: YAML Or, to enable session support from within your code, set session => 'YAML'; (However, controlling settings is best done from your config file.) The L backend implements a file-based YAML session storage to help with debugging, but shouldn't be used on production systems. There are other session backends, such as L, which are recommended for production use. You can then use the L keyword to manipulate the session: =head3 Storing data in the session Storing data in the session is as easy as: session varname => 'value'; =head3 Retrieving data from the session Retrieving data from the session is as easy as: session('varname') Or, alternatively, session->read("varname") =head3 Controlling where sessions are stored For disc-based session backends like L, session files are written to the session dir specified by the C setting, which defaults to C<./sessions> if not specifically set. If you need to control where session files are created, you can do so quickly and easily within your config file, for example: session: YAML engines: session: YAML: session_dir: /tmp/dancer-sessions If the directory you specify does not exist, Dancer2 will attempt to create it for you. =head3 Changing session ID If you wish to change the session ID (for example on privilege level change): my $new_session_id = app->change_session_id =head3 Destroying a session When you're done with your session, you can destroy it: app->destroy_session =head2 Sessions and logging in A common requirement is to check the user is logged in, and, if not, require them to log in before continuing. This can easily be handled using a before hook to check their session: use Dancer2; set session => "Simple"; hook before => sub { if (!session('user') && request->path !~ m{^/login}) { forward '/login', { requested_path => request->path }; } }; get '/' => sub { return "Home Page"; }; get '/secret' => sub { return "Top Secret Stuff here"; }; get '/login' => sub { # Display a login page; the original URL they requested is available as # query_parameters->get('requested_path'), so could be put in a hidden field in the form template 'login', { path => query_parameters->get('requested_path') }; }; post '/login' => sub { # Validate the username and password they supplied if (body_parameters->get('user') eq 'bob' && body_parameters->get('pass') eq 'letmein') { session user => body_parameters->get('user'); redirect body_parameters->get('path') || '/'; } else { redirect '/login?failed=1'; } }; dance(); Here is what the corresponding C file should look like. You should place it in a directory called C: Session and logging in
User Name : Password:
Of course, you'll probably want to validate your users against a database table, or maybe via IMAP/LDAP/SSH/POP3/local system accounts via PAM etc. L is probably a good starting point here! A simple working example of handling authentication against a database table yourself (using L which provides the C keyword, and L to handle salted hashed passwords (well, you wouldn't store your users passwords in the clear, would you?)) follows: post '/login' => sub { my $user_value = body_parameters->get('user'); my $pass_value = body_parameters->get('pass'); my $user = database->quick_select('users', { username => $user_value } ); if (!$user) { warning "Failed login for unrecognised user $user_value"; redirect '/login?failed=1'; } else { if (Crypt::SaltedHash->validate($user->{password}, $pass_value)) { debug "Password correct"; # Logged in successfully session user => $user; redirect body_parameters->get('path') || '/'; } else { debug("Login failed - password incorrect for " . $user_value); redirect '/login?failed=1'; } } }; =head3 Retrieve complete hash stored in session Get complete hash stored in session: my $hash = session; =head2 Writing a session engine In Dancer 2, a session backend consumes the role L. The following example using the Redis session demonstrates how session engines are written in Dancer 2. First thing to do is to create the class for the session engine, we'll name it C: package Dancer2::Session::Redis; use Moo; with 'Dancer2::Core::Role::SessionFactory'; we want our backend to have a handle over a Redis connection. To do that, we'll create an attribute C use JSON; use Redis; use Dancer2::Core::Types; # brings helper for types has redis => ( is => 'rw', isa => InstanceOf['Redis'], lazy => 1, builder => '_build_redis', ); The lazy attribute says to Moo that this attribute will be built (initialized) only when called the first time. It means that the connection to Redis won't be opened until necessary. sub _build_redis { my ($self) = @_; Redis->new( server => $self->server, password => $self->password, encoding => undef, ); } Two more attributes, C and C need to be created. We do this by defining them in the config file. Dancer2 passes anything defined in the config to the engine creation. # config.yml ... engines: session: Redis: server: foo.mydomain.com password: S3Cr3t The server and password entries are now passed to the constructor of the Redis session engine and can be accessed from there. has server => (is => 'ro', required => 1); has password => (is => 'ro'); Next, we define the subroutine C<_retrieve> which will return a session object for a session ID it has passed. Since in this case, sessions are going to be stored in Redis, the session ID will be the key, the session the value. So retrieving is as easy as doing a get and decoding the JSON string returned: sub _retrieve { my ($self, $session_id) = @_; my $json = $self->redis->get($session_id); my $hash = from_json( $json ); return bless $hash, 'Dancer2::Core::Session'; } The C<_flush> method is called by Dancer when the session needs to be stored in the backend. That is actually a write to Redis. The method receives a C object and is supposed to store it. sub _flush { my ($self, $session) = @_; my $json = encode_json( { %{ $session } } ); $self->redis->set($session->id, $json); } For the C<_destroy> method which is supposed to remove a session from the backend, deleting the key from Redis is enough. sub _destroy { my ($self, $session_id) = @_; $self->redis->del($session_id); } The C<_sessions> method which is supposed to list all the session IDs currently stored in the backend is done by listing all the keys that Redis has. sub _sessions { my ($self) = @_; my @keys = $self->redis->keys('*'); return \@keys; } The session engine is now ready. =head3 The Session keyword Dancer2 maintains two session layers. The first layer, L provides a session object which represents the current session. You can read from it as many times as you want, and write to it as many times as you want. The second layer is the session engine (L is one example), which is used in order to implement the reading and writing from the actual storage. This is read only once, when a request comes in (using a cookie whose value is C by default). At the end of a request, all the data you've written will be flushed to the engine itself, which will do the actual write to the storage (whether it's in a hash in memory, in Memcache, or in a database). =head1 TEMPLATES Returning plain content is all well and good for examples or trivial apps, but soon you'll want to use templates to maintain separation between your code and your content. Dancer2 makes this easy. Your route handlers can use the L keyword to render templates. =head2 Views In Dancer2, a file which holds a template is called a I. Views are located in the C directory. You can change this location by changing the setting 'views'. For instance if your templates are located in the 'templates' directory, do the following: set views => path( app->location , 'templates' ); By default, the internal template engine L is used, but you may want to upgrade to L