3 **Why endure plain corn when you can pop it?!**
5 Popcorn is a minimal yet powerful nit web application framework that provides cool
6 features for lazy developpers.
8 Popcorn is built over nitcorn to provide a clean and user friendly interface
9 without all the boiler plate code.
11 ## What does it taste like?
13 Set up is quick and easy as 10 lines of code.
14 Create a file `app.nit` and add the following code:
22 redef fun get(req, res) do res.html "<h1>Hello World!</h1>"
26 app.use("/", new HelloHandler)
27 app.listen("localhost", 3000)
30 The Popcorn app listens on port 3000 for connections.
31 The app responds with "Hello World!" for requests to the root URL (`/`) or **route**.
32 For every other path, it will respond with a **404 Not Found**.
34 The `req` (request) and `res` (response) parameters are the same that nitcorn provides
35 so you can do anything else you would do in your route without Popcorn involved.
37 Run the app with the following command:
40 $ nitc app.nit && ./app
43 Then, load [http://localhost:3000](http://localhost:3000) in a browser to see the output.
45 Here the output using the `curl` command:
51 $ curl localhost:3000/wrong_uri
55 <meta charset="utf-8">
56 <title>Not Found</title>
59 <h1>404 Not Found</h1>
64 This is why we love popcorn!
68 **Routing** refers to determining how an application responds to a client request
69 to a particular endpoint, which is a URI (or path) and a specific HTTP request
70 method GET, POST, PUT or DELETE (other methods are not suported yet).
72 Each route can have one or more handler methods, which are executed when the route is matched.
74 Route handlers definition takes the following form:
82 redef fun METHOD(req, res) do end
87 * `MyHandler` is the name of the handler you will add to the app.
88 * `METHOD` can be replaced by `get`, `post`, `put` or `delete`.
90 The following example responds to GET and POST requests:
98 redef fun get(req, res) do res.send "Got a GET request"
99 redef fun post(req, res) do res.send "Got a POST request"
103 To make your handler responds to a specific route, you have to add it to the app.
105 Respond to POST request on the root route (`/`), the application's home page:
109 app.use("/", new MyHandler)
112 Respond to a request to the `/user` route:
115 app.use("/user", new MyHandler)
118 For more details about routing, see the routing section.
120 ## Serving static files with Popcorn
122 To serve static files such as images, CSS files, and JavaScript files, use the
123 Popcorn built-in handler `StaticHandler`.
125 Pass the name of the directory that contains the static assets to the StaticHandler
126 init method to start serving the files directly.
127 For example, use the following code to serve images, CSS files, and JavaScript files
128 in a directory named `public`:
131 app.use("/", new StaticHandler("public/"))
134 Now, you can load the files that are in the `public` directory:
137 http://localhost:3000/images/trollface.jpg
138 http://localhost:3000/css/style.css
139 http://localhost:3000/js/app.js
140 http://localhost:3000/hello.html
143 Popcorn looks up the files relative to the static directory, so the name of the
144 static directory is not part of the URL.
145 To use multiple static assets directories, add the `StaticHandler` multiple times:
148 app.use("/", new StaticHandler("public/"))
149 app.use("/", new StaticHandler("files/"))
152 Popcorn looks up the files in the order in which you set the static directories
153 with the `use` method.
155 To create a virtual path prefix (where the path does not actually exist in the file system)
156 for files that are served by the `StaticHandler`, specify a mount path for the
157 static directory, as shown below:
160 app.use("/static/", new StaticHandler("public/"))
163 Now, you can load the files that are in the public directory from the `/static`
167 http://localhost:3000/static/images/trollface.jpg
168 http://localhost:3000/static/css/style.css
169 http://localhost:3000/static/js/app.js
170 http://localhost:3000/static/hello.html
173 However, the path that you provide to the `StaticHandler` is relative to the
174 directory from where you launch your app.
175 If you run the app from another directory, it’s safer to use the absolute path of
176 the directory that you want to serve.
180 **Routing** refers to the definition of application end points (URIs) and how
181 they respond to client requests. For an introduction to routing, see the Basic routing
184 The following code is an example of a very basic route.
192 redef fun get(req, res) do res.send "Hello World!"
196 app.use("/", new HelloHandler)
201 A **route method** is derived from one of the HTTP methods, and is attached to an
202 instance of the Handler class.
204 The following code is an example of routes that are defined for the GET and the POST
205 methods to the root of the app.
213 redef fun get(req, res) do res.send "GET request to the homepage"
214 redef fun post(req, res) do res.send "POST request to the homepage"
218 app.use("/", new GetPostHandler)
221 Popcorn supports the following routing methods that correspond to HTTP methods:
222 get, post, put, and delete.
224 The request query string is accessed through the `req` parameter:
230 class QueryStringHandler
233 redef fun get(req, res) do
234 var tpl = new Template
235 tpl.addn "URI: {req.uri}"
236 tpl.addn "Query string: {req.query_string}"
237 for name, arg in req.get_args do
238 tpl.addn "{name}: {arg}"
245 app.use("/", new QueryStringHandler)
246 app.listen("localhost", 3000)
249 Post parameters can also be accessed through the `req` parameter:
258 redef fun post(req, res) do
259 var tpl = new Template
260 tpl.addn "URI: {req.uri}"
261 tpl.addn "Body: {req.body}"
262 for name, arg in req.post_args do
263 tpl.addn "{name}: {arg}"
270 app.use("/", new PostHandler)
271 app.listen("localhost", 3000)
274 There is a special routing method, `all(res, req)`, which is not derived from any
275 HTTP method. This method is used to respond at a path for all request methods.
277 In the following example, the handler will be executed for requests to "/user"
278 whether you are using GET, POST, PUT, DELETE, or any other HTTP request method.
286 redef fun all(req, res) do res.send "Every request to the homepage"
290 Using the `all` method you can also implement other HTTP request methods.
298 redef fun all(req, res) do
299 if req.method == "MERGE" then
301 else super # keep handle GET, POST, PUT and DELETE methods
308 **Route paths**, in combination with a request handlers, define the endpoints at
309 which requests can be made.
310 Route paths can be strings, parameterized strings or glob patterns.
311 Query strings such as `?q=foo`are not part of the route path.
313 Popcorn uses the `Handler::match(uri)` method to match the route paths.
315 Here are some examples of route paths based on strings.
317 This route path will match requests to the root route, `/`.
325 redef fun get(req, res) do res.send "Got a GET request"
329 app.use("/", new MyHandler)
332 This route path will match requests to `/about`.
335 app.use("/about", new MyHandler)
338 This route path will match requests to `/random.text`.
341 app.use("/random.text", new MyHandler)
344 During the query/response process, routes are matched by order of declaration
345 through the `App::use` method.
347 The app declared in this example will try to match the routes in this order:
355 **Route parameters** are variable parts of a route path. They can be used to path
356 arguments within the URI.
\13
357 Parameters in a route are prefixed with a colon `:` like in `:userId`, `:year`.
359 The following example declares a handler `UserHome` that responds with the `user`
368 redef fun get(req, res) do
369 var user = req.param("user")
371 res.send "Hello {user}"
373 res.send("Nothing received", 400)
379 app.use("/:user", new UserHome)
380 app.listen("localhost", 3000)
383 The `UserHome` handler listen to every path matching `/:user`. This can be `/Morriar`,
384 `/10`, ... but not `/Morriar/profile` since route follow the strict matching rule.
388 **Glob routes** are routes that match only on a prefix, thus accepting a wider range
390 Glob routes end with the symbol `*`.
392 Here we define a `UserItem` handler that will respond to any URI matching the prefix
393 `/user/:user/item/:item`.
394 Note that glob route are compatible with route parameters.
402 redef fun get(req, res) do
403 var user = req.param("user")
404 var item = req.param("item")
405 if user == null or item == null then
406 res.send("Nothing received", 400)
408 res.send "Here the item {item} of the use {user}."
414 app.use("/user/:user/item/:item/*", new UserItem)
415 app.listen("localhost", 3000)
420 The methods on the response object (`res`), can is used to manipulate the
421 request-response cycle.
422 If none of these methods are called from a route handler, the client request will
423 receive a `404 Not found` error.
425 * `res.html()` Send a HTML response.
426 * `res.json()` Send a JSON response.
427 * `res.redirect()` Redirect a request.
428 * `res.send()` Send a response of various types.
429 * `res.error()` Set the response status code and send its message as the response body.
433 When the popcorn `App` receives a request, the response cycle is the following:
435 1. `pre-middlewares` lookup matching middlewares registered with `use_before(pre_middleware)`:
436 1. execute matching middleware by registration order
437 2. if a middleware send a response then let the `pre-middlewares` loop continue
438 with the next middleware
439 2. `response-handlers` lookup matching handlers registered with `use(handler)`:
440 1. execute matching middleware by registration order
441 2. if a middleware send a response then stop the `response-handlers` loop
442 3. if no hander matches or sends a response, generate a 404 response
443 3. `post-middlewares` lookup matching handlers registered with `use_after(post_handler)`:
444 1. execute matching middleware by registration order
445 2. if a middleware send a response then let the `post-middlewares` loop continue
446 with the next middleware
452 **Middleware** handlers are handlers that typically do not send `HttpResponse` responses.
453 Middleware handlers can perform the following tasks:
456 * Make changes to the request and the response objects.
457 * End its action and pass to the next handler in the stack.
459 If a middleware handler makes a call to `res.send()`, it provoques the end the
460 request-response cycle and the response is sent to the client.
462 ### Ultra simple logger example
464 Here is an example of a simple “Hello World” Popcorn application.
465 We add a middleware handler to the application called MyLogger that prints a simple
466 log message in the app stdout.
474 redef fun all(req, res) do print "Request Logged!"
480 redef fun get(req, res) do res.send "Hello World!"
485 app.use_before("/*", new MyLogger)
486 app.use("/", new HelloHandler)
487 app.listen("localhost", 3000)
490 By using the `MyLogger` handler to the route `/*` we ensure that every requests
491 (even 404 ones) pass through the middleware handler.
492 This handler just prints “Request Logged!” when a request is received.
494 Be default, the order of middleware execution is that are loaded first are also executed first.
495 To ensure our middleware `MyLogger` will be executed before all the other, we add it
496 with the `use_before` method.
498 ### Ultra cool, more advanced logger example
500 Next, we’ll create a middleware handler called “LogHandler” that prints the requested
501 uri, the response status and the time it took to Popcorn to process the request.
503 This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
509 redef class HttpRequest
510 # Time that request was received by the Popcorn app.
511 var timer: nullable Clock = null
514 class RequestTimeHandler
517 redef fun all(req, res) do req.timer = new Clock
523 redef fun all(req, res) do
524 var timer = req.timer
525 if timer != null then
526 print "{req.method} {req.uri} {res.color_status} ({timer.total}s)"
528 print "{req.method} {req.uri} {res.color_status}"
536 redef fun get(req, res) do res.send "Hello World!"
540 app.use_before("/*", new RequestTimeHandler)
541 app.use("/", new HelloHandler)
542 app.use_after("/*", new LogHandler)
543 app.listen("localhost", 3000)
546 First, we attach a new attribute `timer` to every `HttpRequest`.
547 Doing so we can access our data from all handlers that import our module, directly
548 from the `req` parameter.
550 We use the new middleware called `RequestTimeHandler` to initialize the request timer.
551 Because of the `use_before` method, the `RequestTimeHandler` middleware will be executed
552 before all the others.
554 We then let the `HelloHandler` produce the response.
556 Finally, our `LogHandler` will display a bunch of data and use the request `timer`
557 to display the time it took to process the request.
558 Because of the `use_after` method, the `LogHandler` middleware will be executed after
561 The app now uses the `RequestTimeHandler` middleware for every requests received
563 The page is processed the `HelloHandler` to display the index page.
564 And, before every response is sent, the `LogHandler` is activated to display the
567 Because you have access to the request object, the response object, and all the
568 Popcorn API, the possibilities with middleware functions are endless.
570 ### Built-in middlewares
572 Starting with version 0.1, Popcorn provide a set of built-in middleware that can
573 be used to develop your app faster.
575 * `RequestClock`: initializes requests clock.
576 * `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
577 * `SessionInit`: initializes requests session (see the `Sessions` section).
578 * `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
579 * `Router`: a mountable mini-app (see the `Mountable routers` section).
583 Use the `Router` class to create modular, mountable route handlers.
584 A Router instance is a complete middleware and routing system; for this reason,
585 it is often referred to as a “mini-app”.
587 The following example creates a router as a module, loads a middleware handler in it,
588 defines some routes, and mounts the router module on a path in the main app.
596 redef fun get(req, res) do res.send "Site Home"
602 redef fun all(req, res) do print "User logged"
608 redef fun get(req, res) do res.send "User Home"
614 redef fun get(req, res) do res.send "User Profile"
617 var user_router = new Router
618 user_router.use("/*", new UserLogger)
619 user_router.use("/", new UserHome)
620 user_router.use("/profile", new UserProfile)
623 app.use("/", new AppHome)
624 app.use("/user", user_router)
625 app.listen("localhost", 3000)
628 The app will now be able to handle requests to /user and /user/profile, as well
629 as call the `Time` middleware handler that is specific to the route.
633 **Error handling** is based on middleware handlers.
635 Define error-handling middlewares in the same way as other middleware handlers:
640 class SimpleErrorHandler
643 redef fun all(req, res) do
644 if res.status_code != 200 then
645 print "An error occurred! {res.status_code})"
653 redef fun get(req, res) do res.send "Hello World!"
657 app.use("/", new HelloHandler)
658 app.use("/*", new SimpleErrorHandler)
659 app.listen("localhost", 3000)
662 In this example, every non-200 response is caught by the `SimpleErrorHandler`
663 that print an error in stdout.
665 By defining multiple middleware error handlers, you can take multiple action depending
666 on the kind of error or the kind of interface you provide (HTML, XML, JSON...).
668 Here an example of the 404 custom error page in HTML:
674 class HtmlErrorTemplate
678 var message: nullable String
680 redef fun rendering do add """
684 <meta charset="utf-8">
685 <title>{{{message or else status}}}</title>
688 <h1>{{{status}}} {{{message or else ""}}}</h1>
693 class HtmlErrorHandler
696 redef fun all(req, res) do
697 if res.status_code != 200 then
698 res.send(new HtmlErrorTemplate(res.status_code, "An error occurred!"))
704 app.use("/*", new HtmlErrorHandler)
705 app.listen("localhost", 3000)
710 **Sessions** can be used thanks to the built-in `SessionMiddleware`.
712 Here a simple example of login button that define a value in the `req` session.
718 var is_logged = false
724 redef fun get(req, res) do
726 <p>Is logged: {{{req.session.as(not null).is_logged}}}</p>
727 <form action="/" method="POST">
728 <input type="submit" value="Login" />
732 redef fun post(req, res) do
733 req.session.as(not null).is_logged = true
739 app.use("/*", new SessionInit)
740 app.use("/", new AppLogin)
741 app.listen("localhost", 3000)
744 Notice the use of the `SessionInit` on the `/*` route. You must use the
745 `SessionInit` first to initialize the request session.
746 Without that, your request session will be set to `null`.
747 If you don't use sessions in your app, you do not need to include that middleware.
749 ## Database integration
753 If you want to persist your data, Popcorn works well with MongoDB.
755 In this example, we will show how to store and list user with a Mongo database.
757 First let's define a handler that access the database to list all the user.
758 The mongo database reference is passed to the UserList handler through the `db` attribute.
760 Then we define a handler that displays the user creation form on GET requests.
761 POST requests are used to save the user data.
773 redef fun get(req, res) do
774 var users = db.collection("users").find_all(new JsonObject)
776 var tpl = new Template
777 tpl.add "<h1>Users</h1>"
781 <td>{{{user["login"] or else "null"}}}</td>
782 <td>{{{user["password"] or else "null"}}}</td>
795 redef fun get(req, res) do
796 var tpl = new Template
797 tpl.add """<h1>Add a new user</h1>
798 <form action="/new" method="POST">
799 <input type="text" name="login" />
800 <input type="password" name="password" />
801 <input type="submit" value="save" />
806 redef fun post(req, res) do
807 var json = new JsonObject
808 json["login"] = req.post_args["login"]
809 json["password"] = req.post_args["password"]
810 db.collection("users").insert(json)
815 var mongo = new MongoClient("mongodb://localhost:27017/")
816 var db = mongo.database("mongo_example")
819 app.use("/", new UserList(db))
820 app.use("/new", new UserForm(db))
821 app.listen("localhost", 3000)
824 ## Angular.JS integration
826 Loving [AngularJS](https://angularjs.org/)? Popcorn is made for Angular and for you!
828 Using the StaticHandler with a glob route, you can easily redirect all HTTP requests
829 to your angular controller:
835 app.use("/*", new StaticHandler("my-ng-app/"))
836 app.listen("localhost", 3000)
839 See the examples for a more detailed use case working with a JSON API.