Tutorial: How to build one-to-many relations

At the end of this tutorial you will know how to implement and work with one-to-many relations and I will show it to you using pokemons ✨!

This is the longest article I ever wrote due to our our code base getting bigger and bigger. I still gave my best to keep it approachable and overseeable 😊

You can find the result of this tutorial on github here

This tutorial is a natural follow-up of How to write CRUD using Leaf. You can either go for that tutorial first and come back later or be a rebel, skip it and read on 😊

1. Create and generate a new project
2. one-to-many / one-to-one (explanation)
3. Building Model: Pokemon (with relation to User)
4. Adjusting Model: User (defining relation to Pokemons)
5. Building Controller: PokemonController
6. Adjusting Controller: UserController (delete related pokemons)
7. Adjusting View: list all pokemons of a user
8. Adjusting View: implement a form to create new pokemons

We will use the outcome of the aforementioned tutorial as a template to create our new project:

Before we generate an Xcode project we would have to change the package name within Package.swift:

Now in the terminal at the root directory projectName/ execute:

It may take a bit fetching the dependency, but when done you should have a project structure like this:

The first step before we build our models that have relations we will have to figure out what kind of relation we need. Whether we need a many-to-many or one-to-many relation. Why is that? Ohw, I’m glad you asked! Because if we have a many-to-many relation we will need three database tables whereas for one-to-many we only need two. In our scenario we have user and pokemon for whom we want to build relations for. We know a user (poké-trainer) can own more than one pokemon. But a pokemon like a charmander cannot be owned by another user at the same time. That other user might also have a charmander, but that one is not the same. It has a different soul, character, database id 😉.

For our case we will need a User-Model and a Pokemon-Model (the latter has an attribute user_id, so it knows who owns it). That’s two tables:

NOTE: if you now want to know how many pokemons an user owns, you can simple search in the pokemon-table looking for the users id.

For short: If you have a one-to-one relation it’s the same database structure. Two tables. All you need is to check whether an user owns a pokemon aready before you go and create a new one for him. And if he owns one already you just wont allow another creation by your code. That will end in one-to-one 😊

Create a new swift file in Models/ and name it Pokemon.swift

NOTE: I used the terminal: touch Sources/App/Models/Pokemon.swift

You may have to re-generate your Xcode project with vapor xcode -y in order to let Xcode see your new directory.

In Models/Pokemon.swift include the following code:

NOTE: We use User.foreignIdKey here but this is really nothing special, it’s a string. It’s just safer not to type “user_id” ourself. Reduces typos mypos 😊

Now let’s conform our Pokemon-Model to Preparation:

The important part here is builder.foreignId(for: User.self) which is the first part for us to define the relation between our Pokemon and the User.

Since we want to display information about our Pokemon in the view as well, we’ll need to conform the Pokemon-Model to NodeRepresentable:

Now add our Pokemon-Model to preparations in Setup/Config+Setup.swift

If you now cmd+r or run everything should built without any error 😊.

Note: make sure to select Run as a scheme next to your button before running the app

Now to the second part: define the relation between a User and his Pokemon. In our Models/User.swift add the following:

We extend our user here and define a computed property that returns the users children. You can read Children<User, Pokemon> as Children<From, To> so the relation is defined from User-to-Pokemon as in one-to-many. And that’s it for the relation!

Now the only thing left is to add the children to our node within our makeNode() function so we can actually access them in the view:

In our Controllers/ directory create a new file and name it PokemonController.swift

NOTE: I used the terminal, just execute:
touch Sources/App/Controllers/PokemonController.swift

You may have to re-generate your Xcode project with vapor xcode -y in order to let Xcode see your new file.

We’ll write a create function inside Controllers/PokemonController.swift:

Let’s not forget to create a new route for this in our Routes/Routes.swift:

Now that we’re able to create new pokemons for a user we’ll also need to consider them when deleting a user. So add the following line in our Controller/UserController.swift:

we will first adjust our view to also print all pokemons of a user within Resources/Views/crud.leaf:

NOTE: Select crud.leaf and go to Editor>Syntax Coloring>HTML 😉

All we did here is to loop on the property pokemons of a user and print the name for each one 🍃

In the next step we will create a form where we have first: a dropdown to select a user we want the pokemon to be created for and second: the fields (name and level) we need for a new pokemon:

Let me explain what we did here - some things are really important! I assume you are already familiar with how form-tags work from How to write CRUD using Leaf - but we have something new here. We have given our form-tag an id and we have used a select-tag where we refer to our form using that id. This is super important otherwise values from our dropdown wont be sent! This was new to me - I though it will just work as long as it’s within the form. Just the way input-tags work. Ohw how naive.. Turned out you can have your select-tag wherever you want! As long as you refer to your form with form=”id-of-the-form” the values will be sent along as soon as the from is submitted! 🤓 👍🏻

NOTE: In order to sent values from a dropdown (select-tag) alongside a form you need to give your form an id and refer from your select-tag to that form using that id!

Our final cmd+r or run and refresh of our site and that’s it! You successfully implemented one-to-many relations 🎉 🎉 !!

I'm an always optimistic, open minded and knowledge seeking fullstack developer passionate about UI/UX and changing things for the better :)