Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The go-start webframework

The go-start webframework

A high level webframework for Go
#golang

Erik Unger

March 22, 2012
Tweet

More Decks by Erik Unger

Other Decks in Programming

Transcript

  1. Goals • Create a high level web-framework for Go, like

    Django for Python or Rails for Ruby • Be Go-ish • Don’t make stuff more complicated than it has to be • Convention over configuration • Easy setup and deployment (will be improved with Go v1.0) Donnerstag, 22. März 12
  2. Current status • In development for 10 months • Work

    sponsored by STARTeurope • Used in production for http://startuplive.in • Still Go v0.6, upgrade to v1.0 very soon • GoClipse + gb, but will move to Sublime Text 2 and go install with Go v1.0 Donnerstag, 22. März 12
  3. What’s in it? • MVC • Prefer Go syntax to

    template languages • HTML5 Boilerplate out of the box • MongoDB • All batteries included Donnerstag, 22. März 12
  4. All batteries included • HTML5 Boilerplate • jQuery • web.go

    • mustache.go (will be replaced by Go v1.0 template system) • mgo / bson • goconf Donnerstag, 22. März 12
  5. Philosophy • Why learn another template language if you can

    use Go syntax? • DOM tree has a 1:1 Go object representation on the server • Every element has an ID to enable sync of DOM tree and server view representation Donnerstag, 22. März 12
  6. HTML Tags / Shortcuts •H1, H2, ... •P •Pre •A,

    A_blank •Img •Br •B, I, Q •Em, Strong •Ul, Ol Donnerstag, 22. März 12
  7. view := Views{ ! NewDiv("myclass", ! ! H1("Example HTML structure"),

    ! ! P("This is a paragraph"), ! ! P( ! ! ! HTML("Some unescaped HTML:<br/>"), ! ! ! Printf("The number of the beast: %d", 666), ! ! ! Escape("Will be escaped: 666 < 999"), ! ! ), ! ! A_blank("http://go-lang.org", "A very simple link"), ! ), ! Hr(), ! Pre("! <- pre formated text, followed by a list:"), ! Ul("red", "green", "blue"), ! &Template{ ! ! Filename: "mytemplate.html", ! ! GetContext: func(ctx *Context) (interface{}, os.Error) { ! ! ! return map[string]string{"Key": "Value"}, nil ! ! }, ! }, } Donnerstag, 22. März 12
  8. But I ended up using them only for the HTML5

    Boilerplate Page template Donnerstag, 22. März 12
  9. view := NewDynamicView( ! func(context *Context) (view View, err os.Error)

    { ! ! var names []string ! ! i := models.Users.Sort("Name.First").Sort("Name.Last").Iterator(); ! ! for doc := i.Next(); doc != nil; doc = i.Next() { ! ! ! names = append(names, doc.(*models.User).Name.String()) ! ! } ! ! if i.Err() != nil { ! ! ! return nil, i.Err() ! ! }! ! ! ! ! return &List{! // List = higher level abstraction, Ul() = shortcut ! ! ! Class: "my-ol", ! ! ! Ordered: true, ! ! ! Model: EscapeStringsListModel(names), ! ! }, nil ! }, ) Donnerstag, 22. März 12
  10. view := &ModelView{ ! GetModelIterator: func(context *Context) model.Iterator { !

    ! return models.Users.Sort("Name.First").Sort("Name.Last").Iterator() ! }, ! GetModelView: func(model interface{}, context *Context) (view View, err os.Error) { ! ! user := model.(*models.User) ! ! return PrintfEscape("%s, ", user.Name), nil ! }, } Donnerstag, 22. März 12
  11. Homepage := &Page{ ! OnPreRender: func(page *Page, context *Context) (err

    os.Error) { ! ! context.Data = &PerPageData{...} // Set global page data at request context ! }, ! WriteTitle: func(context *Context, writer io.Writer) (err os.Error) { ! ! writer.Write([]byte(context.Data.(*PerPageData).DynamicTitle)) ! ! return nil ! }, ! CSS: HomepageCSS, ! WriteHeader: RSS("go-start.org RSS Feed", &RssFeed) ! WriteScripts: PageWriters( ! ! Config.Page.DefaultWriteScripts, ! ! JQuery, // jQuery/UI is built-in ! ! JQueryUI, ! ! JQueryUIAutocompleteFromURL(".select-username", IndirectURL(&API_Usernames), 2), ! ! GoogleAnalytics(GoogleAnalyticsID), // Google Analytics is built-in ! ) ! Content: Views{}, } Donnerstag, 22. März 12
  12. Admin_Auth := NewBasicAuth("go-start.org", "admin", "password123") func Paths() *ViewPath { !

    return &ViewPath{View: Homepage, Sub: []ViewPath{ // / ! ! {Name: "style.css", View: HomepageCSS}, // /style.css ! ! {Name: "feed", View: RssFeed}, // /feed/ ! ! {Name: "admin", View: Admin, Auth: Admin_Auth, Sub: []ViewPath{ // /admin/ ! ! ! {Name: "user", Args: 1, View: Admin_User, Auth: Admin_Auth},// /admin/user/<ID>/ ! ! }}, ! ! {Name: "api", Sub: []ViewPath{ // 404 because no view defined ! ! ! {Name: "users.json", View: API_Usernames}, // /api/users.json ! ! }}, ! } } Donnerstag, 22. März 12
  13. Models • Models are Go structs (marshaling via reflection) •

    Meta information for validation and display is added via tags • Forms and DB share the same model and validation mechanism • MongoDB is the default database Donnerstag, 22. März 12
  14. type SignupFormModel struct { ! Email model.Email `gostart:"required"` ! Password1

    model.Password `gostart:"required|label=Password|minlen=6"` ! Password2 model.Password `gostart:"label=Repeat password"` } func (self *SignupFormModel) Validate(metaData model.MetaData) []*model.ValidationError { ! if self.Password1 != self.Password2 { ! ! return model.NewValidationErrors(os.NewError("Passwords don't match"), metaData) ! } ! return model.NoValidationErrors } form := &Form{ ! ButtonText: "Signup", ! FormID: "user_signup", ! GetModel: func(form *Form, context *Context) (interface{}, os.Error) { ! ! return &SignupFormModel{}, nil ! }, ! OnSubmit: func(form *Form, formModel interface{}, context *Context) (err os.Error) { ! ! m := formModel.(*SignupFormModel) ! ! // ... create user in db and send confirmation email ... ! ! return err ! }, } Donnerstag, 22. März 12
  15. type ExampleDoc struct { ! mongo.DocumentBase `bson:",inline"` // Give it

    a Mongo ID ! Person mongo.Ref `gostart:"to=people"` // Mongo ID ref to a document in "people" collection ! LongerText model.Text `gostart:"rows=5|cols=80|maxlen=400"` ! Integer model.Int `gostart:"min=1|max=100"` ! Email model.Email // Normalization + special treament in forms ! PhoneNumber model.Phone // Normalization + special treament in forms ! Password model.Password // Hashed + special treament in forms ! SubDoc struct { ! ! Day model.Date ! ! RealFloat model.Float `gostart:"valid" ! ! Drinks []mongo.Choice `gostart:"options=Beer,Wine,Water"` ! } } Donnerstag, 22. März 12
  16. i := models.Users.Filter("Name.Last", "Smith").Sort("Name.First").Iterator(); for doc := i.Next(); doc !=

    nil; doc = i.Next() { ! user := doc.(*models.User) ! // ... } // Err() returns any error after Next() returned nil: if i.Err() != nil { ! panic(i.Err()) } Donnerstag, 22. März 12
  17. user := models.Users.NewDocument().(*models.User) user.Name.First.Set("Erik") user.Name.Last.Set("Unger") doc, err := models.Groups.Filter(“Name”, “testgroup”).One()

    group := doc.(*models.Group) user.Group.Set(group) // sets a mongo.Ref to the group ! err := user.Save() Donnerstag, 22. März 12
  18. Additional packages • Email (message creation missing in standard mail

    package + Google Mail defaults) • Gravatar • RSS parsing • Amiando event management (used by http://startuplive.in) Donnerstag, 22. März 12