Module 1 - Introduction
1. Welcome to the course2. Why Go3. Why start and build a blog?4. What about React/Vue/Angular?5. Getting setup and source filesModule 2 - Tech Stack Walkthrough
1. Introduction to Golang Part 12. Introduction to Golang Part 23. Introduction to Golang Part 34. Structuring Golang Applications5. Templating with Templ6. Just enough interactivity with HTMX7. Getting started with postgres8. Servers, routers and endpointsModule 3 - Creating the MVP
1. What are the minimal requirements?2. Doing some initial plumbing3. Embedding static assets4. Creating our first views5. Tailwind & Utility-first CSS6. Styling the Landing Page7. Styling the Article PageModule 4 - Managing Content
1. Choose your own adventure2. Writing in Markdown3. Parsing Markdown to HTML4. Frontmatter and Meta Information5. Making our code examples look nice6. Adding error pagesModule 5 - Adding the Database
1. What is a Migration?2. Our first migration: articles table3. Creating the Database Layer4. Showing the Latest Posts5. Slugs and Human Readable URLsModule 6 - Managing the Blog
1. What are the minimum requirements?2. A new layout approaches3. Creating a rough outline4. Our second migration: Users Table5. Introduction to authentication6. Storing passwords securely7. Authenticating users8. Remember me9. Managing posts using a hypermedia API10. Our third migration: Altering Posts Table11. Only show posts marked readyModule 7 - Adding Subscribers
1. What are the minimum requirements?2. Our fourth migration: Subscribers Table3. Creating the subscription form4. Adding some interactivity with HTMX5. Saving new subscribers in the database6. Verifying subscriber emails7. Our fifth migration: Tokens Table8. Email validation view9. Email validation tokens10. Sending validation emails with SES11. Making it all come togetherTech Stack Walkthrough
Introduction to Golang Part 2
Summary
In this video, I explain the difference between using uppercase and lowercase names in Go and how they determine the public or private visibility of functions and variables. I demonstrate this through an example, where I create an authentication package with two functions. The video highlights how exporting functions creates a contract with other packages, allowing them to use certain functions. The focus is on how visibility is managed within Go packages for better code organization.
Transcript
You might have noticed that sometimes you use uppercase names and other times they use lowercase names. This is not really by accident because this is how you export something or make something either public or private in Go. This is mostly related to packages and we will touch upon packages soon. But for now, let me just do a quick example so you can see what I mean. So if you create something, let's say we have authentication package, right? So we create a new directory called auth. We go into auth and then we say, let's just create auth.go. We create a package called auth and then let's create two functions, right? We create one that's called authenticate me. And in here, we can also have like functionality that we only want to be available in this package, right? So whenever you export something from a package, you are essentially creating a contract with all the other packages saying like, hey, you can use this if you want to and we don't really want to break that. So let's say we have another one that's in charge of validating, I don't know, passwords. So validate password, right? Now, in this case, the authenticate me we can use throughout our program. We just need to import the auth package and we can use this function. But validate password will stay hidden unless you're inside the auth packages. So if we go back to main.go here, we should be able to say auth right there. And you can see we can only import the authenticate me because it's exported, it's uppercased, right? So this is how you mainly manage private and public functions in Go. Go is not an object-oriented language and does not have inheritance. It uses compositions where you combine structs to reflect a larger structure. This could be that we have, let's say, a wheel, a chassis, some brakes, structures that you didn't combine into a larger structure, a car in this case. To create a structure, or as they're known in Go, a struct, you use the type keyword, then the name of the struct, then the struct keyword, and then brackets where you can specify the fields of your struct. A simple user struct could look something like this. So let's jump down here and say type user and then struct. And then you can define the fields that this user structure has, right? And let's just say it has for now an ID field that is a UUID. And as I mentioned, Go does not have inheritance, it has composition. So let's say that we are creating a banking, some financial system. So we might have the concept of a user, right? And then a concept of an account. And this account would have also an ID. And let's say it has a balance as well, that's an int64. And to reflect this in code, that a user and account belongs together somehow, we might also have the concept of a wallet. So this wallet would again have an ID, and then it would have a user and an account associated with it, right? Now, since we don't have inheritance, account and user does not know anything about wallet. It's only wallet who knows about account and user. And we can define methods sort of similar as you would in object-oriented programming, where they have like the typical example is you have, what, a dog class that has the method bug or something like that. We can do the same thing in Go. So by saying, instead of type, say, func, point it to wallet. And let's say we have a method called withdraw. And it just returns some integer amount, right? Now, to do this, we will say return wallet. We do it from account. And we just return the account balance. And then say amount is also int64. So we just return this. This is not really realistic, right? You need to update the balance. You need to check if you have enough funds and all of those kind of things. But essentially, this is how you define a method. Now, account and user does not know anything about wallet. So they would not be able to access withdraw. It's only available through wallet. And we need to have a user and account before we can create a wallet struct. So this is how you build structures and how you reflect whatever your domain, your business area revolves with in Go. Go uses duck typing. And what this means is that if some structure has certain type of behavior, things like methods, like on a wallet struct, it can be described by that behavior. It is called that because of the phrase, if it quacked like a duck, walks like a duck, it's probably a duck. If you take our wallet from earlier, we might have a scenario where we have many different types of wallets. But if we were to do a transaction, like withdraw money from the wallet, we're only really interested in the fact that the wallet have a method that's able to withdraw funds. So we can describe that using an interface. We can describe this in code by creating an interface. And this is very similar to how you create structs. You provide the type keyword and the name of the interface, and then the interface keyword. You typically see interfaces in Go ending on ER to indicate behavior. So if you were to look up in the standard library, you will see something like the stringer interface or the iowriter interface. But this is only a convention. It's not a requirement, so we can call them whatever we want. And for our withdraw interface, we only want the withdraw method. So now that anything that has this method is in the eyes of Go and the compiler, our withdraw, right? And this becomes very handy because we like right now we only have one type of wallet, but let's say we have a wallet of type. Let's say we have a Visa wallet, right? Now, if we were to implement withdraw on the Visa wallet, we could just say that it's a withdrawer, so that we have a method function. There's a function called send money. It takes in an amount. That's an int. It's four. And then it takes a wallet that has the withdraw interface on it, and it doesn't care at all what type of wallet. So now, since the wallet up here has the method withdraw, we can pass that to the send money function, but we cannot pass the Visa wallet since it does not have the withdraw function. And if we were to go up and let's here create a wallet that is just our normal wallet, right? And let's ignore the... We actually don't have a return value for send money, so let's just send money. I'm going to send a thousand or whatever money we have, and then we pass the wallet. No issue. But if we say Visa wallets, and it's the Visa wallet, we will now have an error because the Visa wallet does not have the withdraw method. If we were to add it, so we can just do like this and do it on the Visa wallet, our error now goes away because it has this method. So this is how you define interfaces in Golang. It's tempting when learning to use interfaces to use them all the time. However, this will most likely lead to over-engineered spaghetti code unless you have a very deep understanding of the domain you're operating within. As a rule of thumb, these interfaces should be discovered, not created or not thought of beforehand. These are very powerful to create abstractions and polymorphism and should be used with care.