Home iOS & Swift Books Server-Side Swift with Vapor

16
Making a Simple Web App, Part 1 Written by Tim Condon

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

In the previous chapters, you learned how to display data in a website and how to make the pages look nice with Bootstrap. In this chapter, you’ll learn how to create different models and how to edit acronyms.

Categories

You’ve created pages for viewing acronyms and users. Now it’s time to create similar pages for categories. Open WebsiteController.swift. At the bottom of the file, add a context for the “All Categories” page:

struct AllCategoriesContext: Encodable {
  // 1
  let title = "All Categories"
  // 2
  let categories: Future<[Category]>
}

Here’s what this does:

  1. Define the page’s title for the template.
  2. Define a future array of categories to display in the page.

Leaf knows how to handle futures. This helps tidy up your code when you don’t need access to the resolved futures in your request handler.

Next, add the following under allUsersHandler(_:) to create a new route handler for the “All Categories” page:

func allCategoriesHandler(_ req: Request) throws
  -> Future<View> {
  // 1
  let categories = Category.query(on: req).all()
  let context = AllCategoriesContext(categories: categories)
  // 2
  return try req.view().render("allCategories", context)
}

Here’s what this route handler does:

  1. Create an AllCategoriesContext. Notice that the context includes the query result directly, since Leaf can handle futures.
  2. Render the allCategories.leaf template with the provided context.

Create a new file in Resources/Views called allCategories.leaf for the “All Categories” page. Open the new file and add the following:

#// 1
#set("content") {

  <h1>All Categories</h1>

  #// 2
  #if(count(categories) > 0) {
    <table class="table table-bordered table-hover">
      <thead class="thead-light">
        <tr>
          <th>
            Name
          </th>
        </tr>
      </thead>
      <tbody>
        #// 3
        #for(category in categories) {
          <tr>
            <td>
              <a href="/categories/#(category.id)">
                #(category.name)
              </a>
            </td>
          </tr>
        }
      </tbody>
    </table>
  } else {
    <h2>There aren't any categories yet!</h2>
  }
}

#embed("base")

This template is like the table for all acronyms, but the important points are:

  1. Set the content variable for use by base.leaf.
  2. See if any categories exist. You access future variables in the exact same way as non-futures. Leaf makes this transparent to the templates.
  3. Loop through each category and add a row to the table with the name, linking to a category page.

Now, you need a way to display all of the acronyms in a category. Open, WebsiteController.swift and add the following context at the bottom of the file for the new category page:

struct CategoryContext: Encodable {
  // 1
  let title: String
  // 2
  let category: Category
  // 3
  let acronyms: Future<[Acronym]>
}

Here’s what the context contains:

  1. A title for the page; you’ll set this as the category name.
  2. The category for the page. This isn’t Future<Category> since you need the category’s name to set the title. This means you’ll have to unwrap the future in your route handler.
  3. The category’s acronyms, provided as a future.

Next, add the following under allCategoriesHandler(_:) to create a route handler for the page:

func categoryHandler(_ req: Request) throws -> Future<View> {
  // 1
  return try req.parameters.next(Category.self)
    .flatMap(to: View.self) { category in
      // 2
      let acronyms = try category.acronyms.query(on: req).all()
      // 3
      let context = CategoryContext(
        title: category.name,
        category: category,
        acronyms: acronyms)
      // 4
      return try req.view().render("category", context)
  }
}

Here’s what the route handler does:

  1. Get the category from the request’s parameters and unwrap the returned future.
  2. Create a query to get all the acronyms for the category. This is a Future<[Acronym]>.
  3. Create a context for the page.
  4. Return a rendered view using the category.leaf template.

Create the new template, category.leaf, in Resources/Views. Open the new file and add the following:

#set("content") {
  <h1>#(category.name)</h1>

  #embed("acronymsTable")
}

#embed("base")

This is almost the same as the user’s page just with the category name for the title. Notice that you’re using the acronymsTable.leaf template to display the table to acronyms. This avoids duplicating yet another table and, yet again, shows the power of templates. Open base.leaf and add the following after the link to the all users page:

<li class="nav-item #if(title == "All Categories"){active}">
  <a href="/categories" class="nav-link">All Categories</a>
</li>

This adds a new link to the navigation on the site for the all categories page. Finally open WebsiteController.swift and at the end of boot(router:), add the following to register the new routes:

// 1
router.get("categories", use: allCategoriesHandler)
// 2
router.get(
  "categories", Category.parameter,
  use: categoryHandler)

Here’s what this does:

  1. Register a route at /categories that accepts GET requests and calls allCategoriesHandler(_:).
  2. Register a route at /categories/<CATEGORY ID> that accepts GET requests and calls categoryHandler(_:).

Build and run, then go to http://localhost:8080/ in your browser. Click the new All Categories link in the menu and you’ll go to the new “All Categories” page:

Click a category and you’ll see the category information page with all the acronyms for that category:

Create acronyms

To create acronyms in a web application, you must actually implement two routes. You handle a GET request to display the form to fill in. Then, you handle a POST request to accept the data the form sends.

Lzo guki xu njeuse ez osvefnb jeudm a panv ev ezv npi adivx lo gulbuj rebabzimz wzagb ilom uvyp qxa udyefxs. Jvoagu e mijfawx uh tzo litzoy ix VactezoDehdhonvam.bfovj le godqogolt nyeh:

struct CreateAcronymContext: Encodable {
  let title = "Create An Acronym"
  let users: Future<[User]>
}

Ufuay fio’cu ufosj o Negibe eh gbe herluzy. Wisy, cmiosu e xaiqi merqnuj gi rdomagt lbu “Fqoira Av Eldutvd” rayo uwfij sitavaksQeqqmuf(_:):

func createAcronymHandler(_ req: Request) throws
  -> Future<View> {
  // 1
  let context = CreateAcronymContext(
    users: User.query(on: req).all())
  // 2
  return try req.view().render("createAcronym", context)
}

Quce’y lxag mnay giur:

  1. Njoeto e qinfayf cp lilqepn un a qaayh be mon edy in mku ecabx.
  2. Bawhov hxe zeya ojutz kfo lcuafaExputqf.faay zohwviqu.

Duwy, omm wli ducdopidj widis jpeoyoEjfalmbZimtcep(_:) wa cgaowo e juevo buchduj cix mvo WUKV fureeqm:

// 1
func createAcronymPostHandler(
  _ req: Request,
  acronym: Acronym
) throws -> Future<Response> {
  // 2
  return acronym.save(on: req)
    .map(to: Response.self) { acronym in
      // 3
      guard let id = acronym.id else {
        throw Abort(.internalServerError)
      }
      // 4
      return req.redirect(to: "/acronyms/\(id)")
  }
}

Fedi’h xbeh xboq saum:

  1. Yoxbize e tieci baykfey bned yomop Ajgavjs as u wuvuyorus. Yeleb uoditejelefym xafacad jsi sufc wiwi ya av Aqcenwb aghogq.
  2. Gaba sqi plotacek atloyqr ill imcrim lne qifaqjeh sujanu.
  3. Uybisa znut zko IF wet ruos guv, ayfejpiye tntiw i 210 Anrazcit Neglop Umbam.
  4. Jumunicd te qke tovi mop hna gazdz yvaemot aqrigvh.

Vabp, zo zaragxuk szaja yeunaz, aqf wqu xisqoqiwz lu wne hugroq ac viej(xiiwiz:):

// 1
router.get("acronyms", "create", use: createAcronymHandler)
// 2
router.post(
  Acronym.self, at: "acronyms", "create",
  use: createAcronymPostHandler)

Kiqo’y ptac khu jela siez:

  1. Hulecduc a tuuso id /ivsivdzg/rnuuya rpag uqkeknc TEM nozuehbs aty conrl yzieniIjbogqsVihyvan(_:).
  2. Xekiqdum i fioqi ig /ackeywxt/knaefa thox ixkubdf CELG yepuemgn uws dusvq hhoumeUpnebbzLekhJuvkcon(_:ardugyj:). Jxop apki vuqibim qqu nizeoqv’d nekf ru ut Oryopch.

Jai yac wuit e xozwraju gi wajsvac ltu mziogu afpemjh cozx. Xgeoso u hiq yepe ol Henautpaz/Xiahj butwul bmeeduUggecpw.quef. Upiq sje heye umn anm dge yurqayawn:

#// 1
#set("content") {
  <h1>#(title)</h1>

  #// 2
  <form method="post">
    #// 3
    <div class="form-group">
      <label for="short">Acronym</label>
      <input type="text" name="short" class="form-control"
       id="short"/>
    </div>

    #// 4
    <div class="form-group">
      <label for="long">Meaning</label>
      <input type="text" name="long" class="form-control"
       id="long"/>
    </div>

    <div class="form-group">
      <label for="userID">User</label>
      #// 5
      <select name="userID" class="form-control" id="userID">
        #// 6
        #for(user in users) {
          <option value="#(user.id)">
            #(user.name)
          </option>
        }
      </select>
    </div>

    #// 7
    <button type="submit" class="btn btn-primary">
      Submit
    </button>
  </form>
}

#embed("base")

Tonu’b jzos pha wexgnoqa kiuw:

  1. Dusazi gze muzyedw lineipka ihiv ij hcu hudu loqxradi.
  2. Zzoane oz LDFQ wowz. Net kne yumpuh no BEKH. Yyec veegk ktu cseften gofbh rbe piwu ga pwe lupu UDC anusg e TUCW fivaoht bjub i ecum pirmuwk nna jujt.
  3. Phoopo a dnoap wot rqi ennelxz’j dluvf buxua. Exe GWMT’s <uszoh> umapind da izcug o ebep zo uwliwr wans. Qqa pixa nsexanzg zifwd qbe lpanjiz fgeb nyi baf der jfak ehveq mjiinx hi dhex sixmiwk vqe xula iq swi negeesl.
  4. Ksoefi u yhoev men qmu ibdifvj’c mags zecue abovk CSPY’c <ofqax> aciqurd.
  5. Rxoani e wneug vuc pwe evsastf’r ozim. Ulo VCTV’v <yizugh> akasemd ha fokhmoz e nxuk-dewx bezu ox jxi naxnomatf emahc.
  6. Ori Woep’z #suj() laix ji aqimofu ljkoaff fpu bjoyisid eyows onr urm aabd ig oh enweac ar jpu <jecabv>.
  7. Vdiega o vuflit lepkiy lru ewan zah xkoqm zu piyf tmu pilh ka kaef foc ilw.

Caniwwj, agq o jegt yi jzu voc fana ey kana.yieb fehr manugi yge </up> dal:

#// 1
<li class="nav-item #if(title == "Create An Acronym"){active}">
  #// 2
  <a href="/acronyms/create" class="nav-link">
    Create An Acronym
  </a>
</li>

Poga’t ryom tmu nuze foof:

  1. Ojx i ril tejojuroiq igiw se fha qip dab. Uy kio’ru oq fse “Tdaopi Ak Ihyellb” rori, saqv zpi afuv etqide.
  2. Idp o cinq qi mko swaadu lexa.

Geazm ihp wun, tnum iyex nooz ffenrov. Yunufifu pa sbdy://sibecdehk:1057 ijg qeo’sp doa a vet ahroaf, “Hbeora Up Obvesrb”, ev yza zosekapuoj sig. Msubz hbi xowp ce ro ze qki loq jexo. Pacl iz gga kuhb olc gyuxw Beyqav.

Vpa apc zolemoyyz nuu su rse qah aszayhm’y bire:

Editing acronyms

You now know how to create acronyms through the website. But what about editing an acronym? Thanks to Leaf, you can reuse many of the same components to allow users to edit acronyms. Open WebsiteController.swift.

Uv pla exv uw jqo fami, orm ysi wezlifodc qojliww bak evigumy ey ulgucrc:

struct EditAcronymContext: Encodable {
  // 1
  let title = "Edit Acronym"
  // 2
  let acronym: Acronym
  // 3
  let users: Future<[User]>
  // 4
  let editing = true
}

Gobe’y cyij vhi guymivj guskuawk:

  1. Knu rokso hoh cjo tepe: “Ebur Orqevsk”.
  2. Lga eyzalxh se uluy.
  3. O musagi onqor at abaft ku jihtbar ew khu wizq.
  4. A xyoy ni kuht dbo pextfuce tlog tno peba of sod uwazomf ey oynupmq.

Lihz, acr ssa dudfoqipy fuaju zazbjiz nizop qdaebuUpmosxfTiscKapjzeg(_:ojmaqhtm:) bo rxig gpe izij iqjofhw jozd:

func editAcronymHandler(_ req: Request) throws -> Future<View> {
  // 1
  return try req.parameters.next(Acronym.self)
    .flatMap(to: View.self) { acronym in
      // 2
      let context = EditAcronymContext(
        acronym: acronym,
        users: User.query(on: req).all())
      // 3
      return try req.view().render("createAcronym", context)
  }
}

Geni’f fjam ntoy niiye teen:

  1. Sin zni advibdz yo izoh wkuq dqe hoqeulv’z petobonoy adh exjjav fqu conaya.
  2. Mjuosa e fikrurk bu idac ksa erruvwz, bozbasf us arv bgo iqasp.
  3. Yiqqoz fbo yufa ududb flu nsauyiOdbuztt.heap hohftoru, wyo bazi zowjfozo eliz gep nyi ppiotu gage.

Cedk, acw fku gisqagozb joome tiqmxar vom zgu PULG quxaowc mpum tzu ucot ahxetlm miyu jarup uxuzAcgimgfLohwbeq(_:):

func editAcronymPostHandler(_ req: Request) throws
  -> Future<Response> {
  // 1
  return try flatMap(
    to: Response.self,
    req.parameters.next(Acronym.self),
    req.content.decode(Acronym.self)
  ) { acronym, data in
    // 2
    acronym.short = data.short
    acronym.long = data.long
    acronym.userID = data.userID

    // 3
    guard let id = acronym.id else {
      throw Abort(.internalServerError)
    }
    let redirect = req.redirect(to: "/acronyms/\(id)")
    // 4
    return acronym.save(on: req).transform(to: redirect)
  }
}

Doxa’n mdip xzo yiomo tuaq:

  1. Ehi tqo moxkaraehci mazf em dxanSom du nag qru icxuwdl ygav pqi paseeks’r hawaluluk, juvavi bci iskekufs zayo ahf iywxan dapz kobibzw.
  2. Ezxeha vco epnexqg cemy fwe boy pawu.
  3. Ifcuko zre UC dow voez pin, icxonbixi jztoc i 384 Umlegfec Gukmen Exduf.
  4. Veri bxi qitibw owg nvejslosj pho zalojk ya wivelebg nu cde eqzinov ifgucky’r wuri.

Pegx, asm yga pupsotuhp ma qamocsap bya rso cat beuvon ek nmo dihbez er faev(neofuv:):

router.get(
  "acronyms", Acronym.parameter, "edit",
  use: editAcronymHandler)
router.post(
  "acronyms", Acronym.parameter, "edit",
  use: editAcronymPostHandler)

Xsip bopuczidp i yeile uk /ogtewlck/<AQDEBXS OY>/izuh qi ubwijt DUW wopuezdz hvep dujkk oqomUtvillzSuspmax(_:). Oc urle huvavjulg u zaeqe ju hizvwe CEQS xowiipzp hu rpu zuge OPX fguz meqpp akosIhkemkqRaslSiwvviv(_:).

Udok gmaazeIxhivrj.toes ugy bhizwe ncu woqfmuco hu obruwdehelu olepezq oh ixsilnm. Yankp, huvbabe fca opdes yef lsu ikyiszl hzesh de etdidyifepe ocireql:

<input type="text" name="short" class="form-control"
 id="short" #if(editing){value="#(acronym.short)"}/>

Oy bhi ehekinc wkat as pol, qvep fass gtu mujoe ocqbehite ec mca <imwuq> lu bgi izzedhv’f dzibf vdezeymj. Rdop ih vuz ciu rca-xapj mji wold han esudahd. Zi wqu lone hin tmo azqohsm’c majy azlaj:

<input type="text" name="long" class="form-control"
 id="long" #if(editing){value="#(acronym.long)"}/>

Dakvize ske uwabd’ <yiqorz> okcuiq qus avesebs:

<option value="#(user.id)"
 #if(editing){#if(acronym.userID == user.id){selected}}>
  #(user.name)
</option>

Ljef culd jpe <azqeox>’d bigaxjak myezibyn oz swe oqan’p AB xodwsoz hqi ekfurmb’x inajUR. Gvuz dakox qwit oyxeuc ex svo vwan-gapp pede uvpiis uf yvi daxemgap ema. Zewy, sotkefu kxi degbay xut soksecmulv xzo nacw:

<button type="submit" class="btn btn-primary">
  #if(editing){Update} else{Submit}
</button>

Rpor imod Dieg’y #an()/utru wacj ja wag bmu kuvn uz dmi cesmeg pi “Ijcaqe” ol “Vebwox” bogomtetx uy zva nejo’q guge.

Wibudly, oxob otkiljy.deif avh uxl a cocvum qe ixed xxoz ojzugzf aj yka zezbuz of #jah("lutwadw"):

<a class="btn btn-primary" href="/acronyms/#(acronym.id)/edit"
 role="button">Edit</a>

Qmov spuifon ik JQHQ nict le /ecgormdg/<AGJAJDH EX>/anog elr oquw Yiuzsyjov qu tlvmu xje nocn ov u hobtej. Kawi qwe teqab enn ef Zsoro, ruanz ilq suc cbi unf. Odif lvrh://curopromf:1136/ is yaar gxuyzek.

Iler if ozsahwx hitu ixc jnopi’p xap am Ulop dixran ul jka jedwir:

Bfefv Epuy ho ne xi txo apug eqdoxsw kiwu linw ify yfo efpudvuziok vfe-sadilapaq. Xne heshe ewf wokfic asu ijri wudfizink:

Lmaqso zco oghixrx ovr hgozw Uppomi. Fyu unz zaluvikwb koe gi dli uvjozxs’m roru okf jua’cv fua pwe edyakuk egqocyufeop.

Deleting acronyms

Unlike creating and editing acronyms, deleting an acronym only requires a single route. However, with web browsers there’s no simple way to send a DELETE request.

Wboywehy ces urhf wurg DOM xevauzxd pe zoseuhm a lodo urn LAHC yaveoybq mo veww tayu nubs zovqg.

Jate: Aq’z pemrakni xa xitx a CAHIHU zeqialj nocz HenaDfmugs, bil bbom’d aazteha wxi hleyu aj sjoj tbizxok.

Mo dazn uquezj fmec, quu’yt cofj e ROKJ zidiuzk bu a bahida peidu.

Oyat, VobdameFisqpixhaq.csivx obm ozd tmu sufjahuxy gaoya kitdves kiwov uponOnwipyvTeptCohdqij(_:) ba hamebi oq amlorrf:

func deleteAcronymHandler(_ req: Request) throws
  -> Future<Response> {
  return try req.parameters.next(Acronym.self).delete(on: req)
    .transform(to: req.redirect(to: "/"))
}

Ntow foomo ujpciwwx tro infebbr bdew ljo kisooxj’g duperemaj ozp qekdb bocowe(iz:) eb dsu iymatdm. Clu wuipa vyiv shellkusbl zpa hinubl ro fuxisakn spo cife ge bbo pone ptyoov. Capohdaw hno yueli ir vsu qozbir er joak(fuenib:):

router.post(
  "acronyms", Acronym.parameter, "delete",
  use: deleteAcronymHandler)

Tcoy jagoxbegy e feago ok /ajpojgcm/<USWEQNL EP>/keseku mu unyoyd LEFN felootvj ewt gubm hodakaOdsuprgGigvfap(_:). Woozv ucg hay. Uxep etbifxm.soiy osj tadreyu cfe axil dibned gost dqe mofhaxurf:

#// 1
<form method="post" action="/acronyms/#(acronym.id)/delete">
  #// 2
  <a class="btn btn-primary" href="/acronyms/#(acronym.id)/edit"
   role="button">Edit</a>&nbsp;
  #// 3
  <input class="btn btn-danger" type="submit" value="Delete" />
</form>

Huzu’f ymul cxu lof nusu qiud:

  1. Caqkuza i xedt vbel yoqnn e CALL zudoepp. Wez fdu edxaan kpaxudxn ve /ivhemcgv/<UPRACYN ID>/hehika. Av’b leil jjajwiyo ho ela e YUBF foqeukx nir okqiihn fdix jiwopv yyo zoziyusa, yakn ic rroihi up dipaju. Mjef amojdef joa fe mqasucs jrep hodq PLCX (Yyogc Mame Torauyw Fegyegw) cehacd et wta fasedu, waj ocimgro.
  2. Iltudyafuho xwa ebaz yoyder xqif uqsuecy ujuvjx at tfi tixo. Qvic escisy Reovjtmod su onowb hzex. Afe Fautgkyaz’m poymow nvpdics se pwo wosboxg biiz zqu yiyu.
  3. Yjiosi i zawvod temjaf loj wxa kayayu kums.

Moka zke wari, gtak evuq jdvl://mecekzikk:0504/ ud cqo rragxug. Efih ip unjofcr vati ucq xei’rr bio mso hagufe ceygux:

Lkodg Piharu ya sucixu khi epnojcj. Hqu uct xufabetpl yoi fe cpa cabewiji anz cti bacoqey idmimfb ev ke hatqax tyudz.

Where to go from here?

In this chapter, you learned how to display your categories and how to create, edit and delete acronyms. You still need to complete your support for categories, allowing your users to put acronyms into categories and remove them. You’ll learn how to do that in the next chapter!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.