Building the Skeleton in Padrino
So in order to get a real feel for how Padrino and Hapi.js will work, I’m going to create the site skeleton and the basic APIs. In addition to the server app framework, I’ve made a number of other technical decisions:
- PicsNearMe has a relational database (sorry Mongo!). The database will be MySQL locally. I plan on taking advantage of Amazon’s new Aurora DB in production.
- For now, the app will serve statically generated HTML for the web front-end. If I feel like getting fancy I’ll add Angular.js later.
- APIs will be spec’d beforehand using Swagger.
- The Swagger spec will be used for unit testing the application.
- Authentication will be OAuth2, with tokens expiring after a day and a re-login needed after 30 days of inactivity (aka the refresh token is good for 30 days, but regular token just for 1). I’ve worked with this model in the past and when coupled with revoking permissions, it works out really well. I wouldn’t recommend this approach with particularly sensitive information.
Testing out the frameworks
For the purposes of giving Hapi and Padrino a “real world test” I won’t be creating the full application. Instead I’ll be creating the user/group system which at a simple level looks something like this:
One thing to note is that I’ve already made the decision to have Photos owned by Groups, even for 1 user. So upon account creation there is automatically one group created just for the user to post pictures to on their own. If I expected this site to have millions of users, I would absolutely revisit this decision. But for now it is a trade-off I’m willing to make in order to have a single code path for posting photos.
I’ll also be skipping authentication, authorization, and permissions for this test, as that is highly language and framework dependent. I’ll get it working at some point.
Here are the endpoints I’ll be creating:
GET /users
POST /users
GET /users/{id}
PUT /users/{id}
DELETE /users/{id}
GET /users/{id}/invitations
PUT /users/{id}/invitations/{id}
GET /groups
POST /groups
GET /groups/{id}
PUT /groups/{id}
POST /groups/{id}/invite
Because I want friendly URLs, the GroupMembership
table will be exposed via the invitations
route. The /groups/{id}/invite
route will actually be the invite creation, again because pretty URLs matter.
Padrino
Getting the Padrino boilerplate setup is pretty painless, except in 0.13.X
all the models are in a separate folder from the rest of the application. This is unacceptable to me so I moved them into /app/models
.
The GroupMembership
model is many-to-many, and I’ve never done this with DataMapper before. I found a blog post that talks about how to create these intermediary models. And that made the db commands not run. So I disabled DataMapper.finalize
from config/boot.rb
and manually created/migrated the db. Starting the app still didn’t work until I took out the fancy many-to-many logic. This was very confusing and took almost an hour to sort out.
My GroupMembership
model ends up looking like this:
class GroupMembership
include DataMapper::Resource
# property <name>, <type>
property :id, Serial
property :status, Enum[:pending, :accepted, :declined], default: :pending
property :roles, Enum[:owner, :member], default: :member
property :created_at, DateTime
property :user_id, Integer
property :group_id, Integer
belongs_to :user
belongs_to :group
end
After models and setting up the db, the next step is getting some output. Unlike more opinionated frameworks, Padrino doesn’t have generators that create all the things. One thing I like is that Padrino takes the HTTP verb and integrates it directly into the controller. There are no separate routes file to edit, which is wonderful. The downside is that for this API I end up writing commands like: padrino g controller Groups get:index post:index put:index
. There’s also no way to indicate params on the command line, so anything that needs an id
for instance needs to be edited. This is the resulting empty controller:
Picsnearme::App.controllers :groups, :provides => [:json] do
get :index do
end
post :index do
end
put :index, with: :id do
end
end
I also need to add a custom route to handle /groups/{id}/invite
. This also can’t be done by the command line generators. Thankfully, that is also intuitive:
post :invite, map: "/groups/:id/invite" do
end
The biggest downside here is that Padrino doesn’t do the pluralization handling like Rails does. So while my model is called User
, I needed to create a Users
controller so URLs would be correctly pluralized.
Fantastic. Now to actually have the routes serve some JSON! Except that doesn’t seem to work the way I expected it to. I tried render
, I tried sinatra-style body
, heck I even tried return
and I could not get the controllers to return just JSON without a view. I finally googled enough to try render json: @users
which then complained about a missing template. I do not want to template out my JSON responses in the same way I create view templates. Data is not a view so I do not want JSON templates in the same place as views rendered as HTML. Those are separate concerns.
Oh and in the process of doing this, the server hung and I had to force-quit it via Activity Monitor. All the blog posts I could find online use the grape
plugin for building APIs, but I don’t want to use grape
.
Well, shit. I guess Padrino alone isn’t good enough to create APIs. I’m not going any further as I’ve learned enough about Padrino to know it won’t work for me. This is an incredibly huge letdown. Hapi will be covered next week (hopefully I get further)!