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.
435 **Middleware** handlers are handlers that typically do not send `HttpResponse` responses.
436 Middleware handlers can perform the following tasks:
439 * Make changes to the request and the response objects.
440 * End its action and pass to the next handler in the stack.
442 If a middleware handler makes a call to `res.send()`, it provoques the end the
443 request-response cycle and the response is sent to the client.
445 ### Ultra simple logger example
447 Here is an example of a simple “Hello World” Popcorn application.
448 We add a middleware handler to the application called MyLogger that prints a simple
449 log message in the app stdout.
457 redef fun all(req, res) do print "Request Logged!"
463 redef fun get(req, res) do res.send "Hello World!"
468 app.use("/*", new MyLogger)
469 app.use("/", new HelloHandler)
470 app.listen("localhost", 3000)
473 By using the `MyLogger` handler to the route `/*` we ensure that every requests
474 (even 404 ones) pass through the middleware handler.
475 This handler just prints “Request Logged!” when a request is received.
477 The order of middleware loading is important: middleware functions that are loaded first are also executed first.
478 In the above example, `MyLogger` will be executed before `HelloHandler`.
480 ### Ultra cool, more advanced logger example
482 Next, we’ll create a middleware handler called “LogHandler” that prints the requested
483 uri, the response status and the time it took to Popcorn to process the request.
485 This example gives a simplified version of the `RequestClock` and `ConsoleLog` middlewares.
491 redef class HttpRequest
492 # Time that request was received by the Popcorn app.
493 var timer: nullable Clock = null
496 class RequestTimeHandler
499 redef fun all(req, res) do req.timer = new Clock
505 redef fun all(req, res) do
506 var timer = req.timer
507 if timer != null then
508 print "{req.method} {req.uri} {res.color_status} ({timer.total})"
510 print "{req.method} {req.uri} {res.color_status}"
518 redef fun get(req, res) do res.send "Hello World!"
522 app.use("/*", new RequestTimeHandler)
523 app.use("/", new HelloHandler)
524 app.use("/*", new LogHandler)
525 app.listen("localhost", 3000)
528 First, we attach a new attribute `timer` to every `HttpRequest`.
529 Doing so we can access our data from all handlers that import our module, directly
530 from the `req` parameter.
532 We use the new middleware called `RequestTimeHandler` to initialize the request timer.
534 Finally, our `LogHandler` will display a bunch of data and use the request `timer`
535 to display the time it took to process the request.
537 The app now uses the `RequestTimeHandler` middleware for every requests received
539 The page is processed the `HelloHandler` to display the index page.
540 And, before every response is sent, the `LogHandler` is activated to display the
543 Because you have access to the request object, the response object, and all the
544 Popcorn API, the possibilities with middleware functions are endless.
546 ### Built-in middlewares
548 Starting with version 0.1, Popcorn provide a set of built-in middleware that can
549 be used to develop your app faster.
551 * `RequestClock`: initializes requests clock.
552 * `ConsoleLog`: displays resquest and response status in console (can be used with `RequestClock`).
553 * `SessionInit`: initializes requests session (see the `Sessions` section).
554 * `StaticServer`: serves static files (see the `Serving static files with Popcorn` section).
555 * `Router`: a mountable mini-app (see the `Mountable routers` section).
559 Use the `Router` class to create modular, mountable route handlers.
560 A Router instance is a complete middleware and routing system; for this reason,
561 it is often referred to as a “mini-app”.
563 The following example creates a router as a module, loads a middleware handler in it,
564 defines some routes, and mounts the router module on a path in the main app.
572 redef fun get(req, res) do res.send "Site Home"
578 redef fun all(req, res) do print "User logged"
584 redef fun get(req, res) do res.send "User Home"
590 redef fun get(req, res) do res.send "User Profile"
593 var user_router = new Router
594 user_router.use("/*", new UserLogger)
595 user_router.use("/", new UserHome)
596 user_router.use("/profile", new UserProfile)
599 app.use("/", new AppHome)
600 app.use("/user", user_router)
601 app.listen("localhost", 3000)
604 The app will now be able to handle requests to /user and /user/profile, as well
605 as call the `Time` middleware handler that is specific to the route.
609 **Error handling** is based on middleware handlers.
611 Define error-handling middlewares in the same way as other middleware handlers:
616 class SimpleErrorHandler
619 redef fun all(req, res) do
620 if res.status_code != 200 then
621 print "An error occurred! {res.status_code})"
629 redef fun get(req, res) do res.send "Hello World!"
633 app.use("/", new HelloHandler)
634 app.use("/*", new SimpleErrorHandler)
635 app.listen("localhost", 3000)
638 In this example, every non-200 response is caught by the `SimpleErrorHandler`
639 that print an error in stdout.
641 By defining multiple middleware error handlers, you can take multiple action depending
642 on the kind of error or the kind of interface you provide (HTML, XML, JSON...).
644 Here an example of the 404 custom error page in HTML:
650 class HtmlErrorTemplate
654 var message: nullable String
656 redef fun rendering do add """
660 <meta charset="utf-8">
661 <title>{{{message or else status}}}</title>
664 <h1>{{{status}}} {{{message or else ""}}}</h1>
669 class HtmlErrorHandler
672 redef fun all(req, res) do
673 if res.status_code != 200 then
674 res.send(new HtmlErrorTemplate(res.status_code, "An error occurred!"))
680 app.use("/*", new HtmlErrorHandler)
681 app.listen("localhost", 3000)
686 **Sessions** can be used thanks to the built-in `SessionMiddleware`.
688 Here a simple example of login button that define a value in the `req` session.
694 var is_logged = false
700 redef fun get(req, res) do
702 <p>Is logged: {{{req.session.as(not null).is_logged}}}</p>
703 <form action="/" method="POST">
704 <input type="submit" value="Login" />
708 redef fun post(req, res) do
709 req.session.as(not null).is_logged = true
715 app.use("/*", new SessionInit)
716 app.use("/", new AppLogin)
717 app.listen("localhost", 3000)
720 Notice the use of the `SessionInit` on the `/*` route. You must use the
721 `SessionInit` first to initialize the request session.
722 Without that, your request session will be set to `null`.
723 If you don't use sessions in your app, you do not need to include that middleware.
725 ## Database integration
729 If you want to persist your data, Popcorn works well with MongoDB.
731 In this example, we will show how to store and list user with a Mongo database.
733 First let's define a handler that access the database to list all the user.
734 The mongo database reference is passed to the UserList handler through the `db` attribute.
736 Then we define a handler that displays the user creation form on GET requests.
737 POST requests are used to save the user data.
749 redef fun get(req, res) do
750 var users = db.collection("users").find_all(new JsonObject)
752 var tpl = new Template
753 tpl.add "<h1>Users</h1>"
757 <td>{{{user["login"] or else "null"}}}</td>
758 <td>{{{user["password"] or else "null"}}}</td>
771 redef fun get(req, res) do
772 var tpl = new Template
773 tpl.add """<h1>Add a new user</h1>
774 <form action="/new" method="POST">
775 <input type="text" name="login" />
776 <input type="password" name="password" />
777 <input type="submit" value="save" />
782 redef fun post(req, res) do
783 var json = new JsonObject
784 json["login"] = req.post_args["login"]
785 json["password"] = req.post_args["password"]
786 db.collection("users").insert(json)
791 var mongo = new MongoClient("mongodb://localhost:27017/")
792 var db = mongo.database("mongo_example")
795 app.use("/", new UserList(db))
796 app.use("/new", new UserForm(db))
797 app.listen("localhost", 3000)
800 ## Angular.JS integration
802 Loving [AngularJS](https://angularjs.org/)? Popcorn is made for Angular and for you!
804 Using the StaticHandler with a glob route, you can easily redirect all HTTP requests
805 to your angular controller:
811 app.use("/*", new StaticHandler("my-ng-app/"))
812 app.listen("localhost", 3000)
815 See the examples for a more detailed use case working with a JSON API.