go-start
A high level web-framework for Go
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
Current status

• In development for 10 months
• Work sponsored by STARTeurope
• Used in production for http://startuplive.in
• Go v1.0
What’s in it?

• MVC
• Prefer Go syntax to template languages
• HTML5 Boilerplate out of the box
• MongoDB
• All batteries included
All batteries included

• HTML5 Boilerplate
• jQuery
• External dependencies fetched
  automatically:
  go get -u github.com/ungerik/go-start
Views
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
Higher level abstractions
• view.List      • view.Menu
• view.Table     • view.Video
• view.Form      • view.Page
• view.ModelView
• view.If
HTML Tags / Shortcuts
• H1, H2, ...   • Br
•P              • B, I, Q
• Pre           • Em, Strong
• A, A_blank    • Ul, Ol
• Img
So how does a view look
         like?
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{}, error) {
! ! ! return map[string]string{"Key": "Value"}, nil
! ! },
! },
}
Yes, templates are
    supported
But I ended up using them
   only for the HTML5
Boilerplate Page template
Dynamic Views
view := NewDynamicView(
!   func(context *Context) (view View, err 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
!   },
)
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 error) {
!   !    user := model.(*models.User)
!   !    return PrintfEscape("%s, ", user.Name), nil
!   },
}
HTML Pages
Homepage := &Page{
!   OnPreRender: func(page *Page, context *Context) (err error) {
!   !    context.Data = &PerPageData{...} // Set global page data at request context
!   },
!   WriteTitle: func(context *Context, writer io.Writer) (err 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{},
}
URL structure
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
!   !    }},
!   }
}
Running the server
view.Init("go-start.org", CookieSecret, "pkg/myproject", "pkg/
gostart")

view.Config.RedirectSubdomains = []string{"www"}
view.Config.Page.DefaultMetaViewport = "width=960px"

view.RunConfigFile(Paths(), "run.config")
Models
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
FormModel
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{}, error) {
!   !    return &SignupFormModel{}, nil
!   },
!   OnSubmit: func(form *Form, formModel interface{}, context *Context) (err error) {
!   !    m := formModel.(*SignupFormModel)
!   !    // ... create user in db and send confirmation email ...
!   !    return err
!   },
}
mongo.Document
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"`
!   }
}
mongo.Collection
var ExampleDocs *mongo.Collection = mongo.NewCollection("exampledocs", (*ExampleDoc)(nil))
mongo.Query
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())
}
Create, modify, save a
 mongo.Document
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()
Additional packages
• Email (message creation missing in standard
  mail package + Google Mail defaults)
• Gravatar
• RSS parsing
• Amiando event management
  (used by http://startuplive.in)
Where to get it


• go get -u github.com/ungerik/go-start
• Documentation: http://go-start.org/
One more thing ;-)
We are hiring!
    STARTeurope
Go+Javascript Pioneers
         (Vienna)
The End
          Questions?

• erik.unger@starteurope.at
• Twitter: @ungerik
• Skype: ungerik

The go-start webframework (GTUG Vienna 27.03.2012)

  • 1.
    go-start A high levelweb-framework for Go
  • 2.
    Goals • Create ahigh 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
  • 3.
    Current status • Indevelopment for 10 months • Work sponsored by STARTeurope • Used in production for http://startuplive.in • Go v1.0
  • 4.
    What’s in it? •MVC • Prefer Go syntax to template languages • HTML5 Boilerplate out of the box • MongoDB • All batteries included
  • 5.
    All batteries included •HTML5 Boilerplate • jQuery • External dependencies fetched automatically: go get -u github.com/ungerik/go-start
  • 6.
  • 7.
    Philosophy • Why learnanother 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
  • 8.
    Higher level abstractions •view.List • view.Menu • view.Table • view.Video • view.Form • view.Page • view.ModelView • view.If
  • 9.
    HTML Tags /Shortcuts • H1, H2, ... • Br •P • B, I, Q • Pre • Em, Strong • A, A_blank • Ul, Ol • Img
  • 10.
    So how doesa view look like?
  • 11.
    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{}, error) { ! ! ! return map[string]string{"Key": "Value"}, nil ! ! }, ! }, }
  • 12.
  • 13.
    But I endedup using them only for the HTML5 Boilerplate Page template
  • 14.
  • 15.
    view := NewDynamicView( ! func(context *Context) (view View, err 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 ! }, )
  • 16.
    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 error) { ! ! user := model.(*models.User) ! ! return PrintfEscape("%s, ", user.Name), nil ! }, }
  • 17.
  • 18.
    Homepage := &Page{ ! OnPreRender: func(page *Page, context *Context) (err error) { ! ! context.Data = &PerPageData{...} // Set global page data at request context ! }, ! WriteTitle: func(context *Context, writer io.Writer) (err 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{}, }
  • 19.
  • 20.
    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 ! ! }}, ! } }
  • 21.
  • 22.
    view.Init("go-start.org", CookieSecret, "pkg/myproject","pkg/ gostart") view.Config.RedirectSubdomains = []string{"www"} view.Config.Page.DefaultMetaViewport = "width=960px" view.RunConfigFile(Paths(), "run.config")
  • 23.
  • 24.
    Models • Models areGo 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
  • 25.
  • 26.
    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{}, error) { ! ! return &SignupFormModel{}, nil ! }, ! OnSubmit: func(form *Form, formModel interface{}, context *Context) (err error) { ! ! m := formModel.(*SignupFormModel) ! ! // ... create user in db and send confirmation email ... ! ! return err ! }, }
  • 27.
  • 28.
    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"` ! } }
  • 29.
  • 30.
    var ExampleDocs *mongo.Collection= mongo.NewCollection("exampledocs", (*ExampleDoc)(nil))
  • 31.
  • 32.
    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()) }
  • 33.
    Create, modify, savea mongo.Document
  • 34.
    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()
  • 35.
    Additional packages • Email(message creation missing in standard mail package + Google Mail defaults) • Gravatar • RSS parsing • Amiando event management (used by http://startuplive.in)
  • 36.
    Where to getit • go get -u github.com/ungerik/go-start • Documentation: http://go-start.org/
  • 37.
  • 40.
    We are hiring! STARTeurope
  • 41.
  • 42.
    The End Questions? • erik.unger@starteurope.at • Twitter: @ungerik • Skype: ungerik