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.
178 In some cases, you can want to redirect request to static files to a default file
179 instead of returning a 404 error.
180 This can be achieved by specifying a default file in the StaticHandler:
183 app.use("/static/", new StaticHandler("public/", "default.html"))
186 This way all non-matched queries to the StaticHandler will be answered with the
191 **Routing** refers to the definition of application end points (URIs) and how
192 they respond to client requests. For an introduction to routing, see the Basic routing
195 The following code is an example of a very basic route.
203 redef fun get(req, res) do res.send "Hello World!"
207 app.use("/", new HelloHandler)
212 A **route method** is derived from one of the HTTP methods, and is attached to an
213 instance of the Handler class.
215 The following code is an example of routes that are defined for the GET and the POST
216 methods to the root of the app.
224 redef fun get(req, res) do res.send "GET request to the homepage"
225 redef fun post(req, res) do res.send "POST request to the homepage"
229 app.use("/", new GetPostHandler)
232 Popcorn supports the following routing methods that correspond to HTTP methods:
233 get, post, put, and delete.
235 The request query string is accessed through the `req` parameter:
241 class QueryStringHandler
244 redef fun get(req, res) do
245 var tpl = new Template
246 tpl.addn "URI: {req.uri}"
247 tpl.addn "Query string: {req.query_string}"
248 for name, arg in req.get_args do
249 tpl.addn "{name}: {arg}"
256 app.use("/", new QueryStringHandler)
257 app.listen("localhost", 3000)
260 Post parameters can also be accessed through the `req` parameter:
269 redef fun post(req, res) do
270 var tpl = new Template
271 tpl.addn "URI: {req.uri}"
272 tpl.addn "Body: {req.body}"
273 for name, arg in req.post_args do
274 tpl.addn "{name}: {arg}"
281 app.use("/", new PostHandler)
282 app.listen("localhost", 3000)
285 There is a special routing method, `all(res, req)`, which is not derived from any
286 HTTP method. This method is used to respond at a path for all request methods.
288 In the following example, the handler will be executed for requests to "/user"
289 whether you are using GET, POST, PUT, DELETE, or any other HTTP request method.
297 redef fun all(req, res) do res.send "Every request to the homepage"
301 Using the `all` method you can also implement other HTTP request methods.
309 redef fun all(req, res) do
310 if req.method == "MERGE" then
312 else super # keep handle GET, POST, PUT and DELETE methods
319 **Route paths**, in combination with a request handlers, define the endpoints at
320 which requests can be made.
321 Route paths can be strings, parameterized strings or glob patterns.
322 Query strings such as `?q=foo`are not part of the route path.
324 Popcorn uses the `Handler::match(uri)` method to match the route paths.
326 Here are some examples of route paths based on strings.
328 This route path will match requests to the root route, `/`.
336 redef fun get(req, res) do res.send "Got a GET request"
340 app.use("/", new MyHandler)
343 This route path will match requests to `/about`.
346 app.use("/about", new MyHandler)
349 This route path will match requests to `/random.text`.
352 app.use("/random.text", new MyHandler)
355 During the query/response process, routes are matched by order of declaration
356 through the `App::use` method.
358 The app declared in this example will try to match the routes in this order:
366 **Route parameters** are variable parts of a route path. They can be used to path
367 arguments within the URI.
368 Parameters in a route are prefixed with a colon `:` like in `:userId`, `:year`.
370 The following example declares a handler `UserHome` that responds with the `user`
379 redef fun get(req, res) do
380 var user = req.param("user")
382 res.send "Hello {user}"
384 res.send("Nothing received", 400)
390 app.use("/:user", new UserHome)
391 app.listen("localhost", 3000)
394 The `UserHome` handler listen to every path matching `/:user`. This can be `/Morriar`,
395 `/10`, ... but not `/Morriar/profile` since route follow the strict matching rule.
399 **Glob routes** are routes that match only on a prefix, thus accepting a wider range
401 Glob routes end with the symbol `*`.
403 Here we define a `UserItem` handler that will respond to any URI matching the prefix
404 `/user/:user/item/:item`.
405 Note that glob route are compatible with route parameters.
413 redef fun get(req, res) do
414 var user = req.param("user")
415 var item = req.param("item")
416 if user == null or item == null then
417 res.send("Nothing received", 400)
419 res.send "Here the item {item} of the use {user}."
425 app.use("/user/:user/item/:item/*", new UserItem)
426 app.listen("localhost", 3000)
431 The methods on the response object (`res`), can is used to manipulate the
432 request-response cycle.
433 If none of these methods are called from a route handler, the client request will
434 receive a `404 Not found` error.
436 * `res.html()` Send a HTML response.
437 * `res.json()` Send a JSON response.
438 * `res.redirect()` Redirect a request.
439 * `res.send()` Send a response of various types.
440 * `res.error()` Set the response status code and send its message as the response body.
444 When the popcorn `App` receives a request, the response cycle is the following:
446 1. `pre-middlewares` lookup matching middlewares registered with `use_before(pre_middleware)`:
447 1. execute matching middleware by registration order
448 2. if a middleware send a response then let the `pre-middlewares` loop continue
449 with the next middleware
450 2. `response-handlers` lookup matching handlers registered with `use(handler)`:
451 1. execute matching middleware by registration order
452 2. if a middleware send a response then stop the `response-handlers` loop
453 3. if no hander matches or sends a response, generate a 404 response
454 3. `post-middlewares` lookup matching handlers registered with `use_after(post_handler)`:
455 1. execute matching middleware by registration order
456 2. if a middleware send a response then let the `post-middlewares` loop continue
457 with the next middleware
463 **Middleware** handlers are handlers that typically do not send `HttpResponse` responses.
464 Middleware handlers can perform the following tasks:
467 * Make changes to the request and the response objects.
468 * End its action and pass to the next handler in the stack.
470 If a middleware handler makes a call to `res.send()`, it provoques the end the
471 request-response cycle and the response is sent to the client.
473 ### Ultra simple logger example
475 Here is an example of a simple “Hello World” Popcorn application.
476 We add a middleware handler to the application called MyLogger that prints a simple
477 log message in the app stdout.
485 redef fun all(req, res) do print "Request Logged!"
491 redef fun get(req, res) do res.send "Hello World!"
496 app.use_before("/*", new MyLogger)
497 app.use("/", new HelloHandler)
498 app.listen("localhost", 3000)
501 By using the `MyLogger` handler to the route `/*` we ensure that every requests
502 (even 404 ones) pass through the middleware handler.
503 This handler just prints “Request Logged!” when a request is received.
505 Be default, the order of middleware execution is that are loaded first are also executed first.
506 To ensure our middleware `MyLogger` will be executed before all the other, we add it
507 with the `use_before` method.
509 ### Ultra cool, more advanced logger example
511 Next, we’ll create a middleware handler called “LogHandler” that prints the requested
512 uri, the response status and the time it took to Popcorn to process the request.
514 This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
520 redef class HttpRequest
521 # Time that request was received by the Popcorn app.
522 var timer: nullable Clock = null
525 class RequestTimeHandler
528 redef fun all(req, res) do req.timer = new Clock
534 redef fun all(req, res) do
535 var timer = req.timer
536 if timer != null then
537 print "{req.method} {req.uri} {res.color_status} ({timer.total}s)"
539 print "{req.method} {req.uri} {res.color_status}"
547 redef fun get(req, res) do res.send "Hello World!"
551 app.use_before("/*", new RequestTimeHandler)
552 app.use("/", new HelloHandler)
553 app.use_after("/*", new LogHandler)
554 app.listen("localhost", 3000)
557 First, we attach a new attribute `timer` to every `HttpRequest`.
558 Doing so we can access our data from all handlers that import our module, directly
559 from the `req` parameter.
561 We use the new middleware called `RequestTimeHandler` to initialize the request timer.
562 Because of the `use_before` method, the `RequestTimeHandler` middleware will be executed
563 before all the others.
565 We then let the `HelloHandler` produce the response.
567 Finally, our `LogHandler` will display a bunch of data and use the request `timer`
568 to display the time it took to process the request.
569 Because of the `use_after` method, the `LogHandler` middleware will be executed after
572 The app now uses the `RequestTimeHandler` middleware for every requests received
574 The page is processed the `HelloHandler` to display the index page.
575 And, before every response is sent, the `LogHandler` is activated to display the
578 Because you have access to the request object, the response object, and all the
579 Popcorn API, the possibilities with middleware functions are endless.
581 ### Built-in middlewares
583 Starting with version 0.1, Popcorn provide a set of built-in middleware that can
584 be used to develop your app faster.
586 * `RequestClock`: initializes requests clock.
587 * `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
588 * `SessionInit`: initializes requests session (see the `Sessions` section).
589 * `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
590 * `Router`: a mountable mini-app (see the `Mountable routers` section).
594 Use the `Router` class to create modular, mountable route handlers.
595 A Router instance is a complete middleware and routing system; for this reason,
596 it is often referred to as a “mini-app”.
598 The following example creates a router as a module, loads a middleware handler in it,
599 defines some routes, and mounts the router module on a path in the main app.
607 redef fun get(req, res) do res.send "Site Home"
613 redef fun all(req, res) do print "User logged"
619 redef fun get(req, res) do res.send "User Home"
625 redef fun get(req, res) do res.send "User Profile"
628 var user_router = new Router
629 user_router.use("/*", new UserLogger)
630 user_router.use("/", new UserHome)
631 user_router.use("/profile", new UserProfile)
634 app.use("/", new AppHome)
635 app.use("/user", user_router)
636 app.listen("localhost", 3000)
639 The app will now be able to handle requests to /user and /user/profile, as well
640 as call the `Time` middleware handler that is specific to the route.
644 **Error handling** is based on middleware handlers.
646 Define error-handling middlewares in the same way as other middleware handlers:
651 class SimpleErrorHandler
654 redef fun all(req, res) do
655 if res.status_code != 200 then
656 print "An error occurred! {res.status_code})"
664 redef fun get(req, res) do res.send "Hello World!"
668 app.use("/", new HelloHandler)
669 app.use("/*", new SimpleErrorHandler)
670 app.listen("localhost", 3000)
673 In this example, every non-200 response is caught by the `SimpleErrorHandler`
674 that print an error in stdout.
676 By defining multiple middleware error handlers, you can take multiple action depending
677 on the kind of error or the kind of interface you provide (HTML, XML, JSON...).
679 Here an example of the 404 custom error page in HTML:
685 class HtmlErrorTemplate
689 var message: nullable String
691 redef fun rendering do add """
695 <meta charset="utf-8">
696 <title>{{{message or else status}}}</title>
699 <h1>{{{status}}} {{{message or else ""}}}</h1>
704 class HtmlErrorHandler
707 redef fun all(req, res) do
708 if res.status_code != 200 then
709 res.send(new HtmlErrorTemplate(res.status_code, "An error occurred!"))
715 app.use("/*", new HtmlErrorHandler)
716 app.listen("localhost", 3000)
721 **Sessions** can be used thanks to the built-in `SessionInit` middleware.
723 Here a simple example of login button that define a value in the `req` session.
729 var is_logged = false
735 redef fun get(req, res) do
737 <p>Is logged: {{{req.session.as(not null).is_logged}}}</p>
738 <form action="/" method="POST">
739 <input type="submit" value="Login" />
743 redef fun post(req, res) do
744 req.session.as(not null).is_logged = true
750 app.use_before("/*", new SessionInit)
751 app.use("/", new AppLogin)
752 app.listen("localhost", 3000)
755 Notice the use of the `SessionInit` on the `/*` route. You must use the
756 `SessionInit` first to initialize the request session.
757 Without that, your request session will be set to `null`.
758 If you don't use sessions in your app, you do not need to include that middleware.
760 ## Database integration
764 If you want to persist your data, Popcorn works well with MongoDB.
766 In this example, we will show how to store and list user with a Mongo database.
768 First let's define a handler that access the database to list all the user.
769 The mongo database reference is passed to the UserList handler through the `db` attribute.
771 Then we define a handler that displays the user creation form on GET requests.
772 POST requests are used to save the user data.
784 redef fun get(req, res) do
785 var users = db.collection("users").find_all(new JsonObject)
787 var tpl = new Template
788 tpl.add "<h1>Users</h1>"
792 <td>{{{user["login"] or else "null"}}}</td>
793 <td>{{{user["password"] or else "null"}}}</td>
806 redef fun get(req, res) do
807 var tpl = new Template
808 tpl.add """<h1>Add a new user</h1>
809 <form action="/new" method="POST">
810 <input type="text" name="login" />
811 <input type="password" name="password" />
812 <input type="submit" value="save" />
817 redef fun post(req, res) do
818 var json = new JsonObject
819 json["login"] = req.post_args["login"]
820 json["password"] = req.post_args["password"]
821 db.collection("users").insert(json)
826 var mongo = new MongoClient("mongodb://localhost:27017/")
827 var db = mongo.database("mongo_example")
830 app.use("/", new UserList(db))
831 app.use("/new", new UserForm(db))
832 app.listen("localhost", 3000)
835 ## Angular.JS integration
837 Loving [AngularJS](https://angularjs.org/)? Popcorn is made for Angular and for you!
839 Using the StaticHandler with a glob route, you can easily redirect all HTTP requests
840 to your angular controller:
846 app.use("/*", new StaticHandler("my-ng-app/", "index.html"))
847 app.listen("localhost", 3000)
850 Because the StaticHandler will not find the angular routes as static files,
851 you must specify the path to the default angular controller.
852 In this example, the StaticHandler will redirect any unknown requests to the `index.html`
855 See the examples for a more detailed use case working with a JSON API.