Build Go Apps That Scale on AWS

Check For Existing User



Build Go Apps That Scale on AWS

Check out a free preview of the full Build Go Apps That Scale on AWS course

The "Check For Existing User" Lesson is part of the full, Build Go Apps That Scale on AWS course featured in this preview video. Here's what you'd learn in this lesson:

Melkey implements two functions to interact with the DynamoDB service. The first function checks for the existence of the user. The second performs the insert operation, adding a record with the username and a plain-text password. Later in the course, the password will be replaced with a hashed version.


Transcript from the "Check For Existing User" Lesson

>> Yeah, let's kinda go back into our database layer and create our functions to register a user. So when I think of registering a user, there's really two main functions that I think makes sense, maybe you agree with it or not. But essentially first is, does this user exists, right?

If we have a bunch of values in our db and we wanna insert a new one, we wanna make sure that it's new, it's unique, it doesn't constrain or conflict with a primary key that we've created earlier. And then the second one is, how do I insert a new record, essentially, into DynamoDB?

Okay, and to help explain this, especially the first part where we're first checking if this user exists, I find it really useful to look at the documentation for getting item from a DynamoDB table using AWS SDK. And you can see here, SDK is supported by a bunch of different languages, Go, Colin, Javascript, Swift is in there, but basically, this is the TLDR of how you can retrieve a value from DynamoDB.

You have this struct, it contains our client, very similar to what we have. We have a struct, it interacts directly with our DynamoDBClient, and then it has this notion of retrieving the item by calling our dbClient. And then, putting all that into a reference of get item input, where we're calling it by the key and the table name.

And that key is primary key constraint, when you insert a value, it's that really, really important actual structure thing that we need to identify of records are being inserted into Dynamo. And there's different properties here of how they choose to put the table name into the struct. I chose it to do a different way, but again, it's one of those things where you can do whatever you want, whatever makes sense to you.

But essentially this is a gist, we're gonna assemble our response using the key and table name properties of the GetItem input struct. Cool, so let's go ahead and do this. So, first I want to create a function that has a receiver to our actual client. So, I think it makes sense to make this you, and we'll do DynamoDBClient().

So that struct can actually call this or do GetUser(). Now that you GetUser, let's actually make two functions, we gonna first DoesUserExist(). And I'm gonna tell you right away, you can combine these two functions into one, you can check if a user exists while retrieving it into one.

I just think it makes a little bit more sense if we separate it, but, yeah, you can definitely combine this into making a more solid function as both purposes, and then this is gonna return a Boolean and an error, okay? Basically does the exist, yes, no. If there's an error, let's send back that error.

And following that DynamoDB documentation, we gonna basically do u.databaseStore, the property that we have on our DynamoDBClient, we hit GetItem, okay? And this GetItem is gonna take our dynamodb.GetItemInput(), and clear that, and the only thing we care about are two values, key and table name. So, before we input our table name, we have to declare it.

And again, I'm working backwards, cuz we don't have a table right now, it doesn't exist, right? So what I'm going to do is I'm gonna create a const all the way up here, and this is gonna house my table name. So TABLE_NAME, and can just state this userTable, okay?

It's fine, we're not gonna get any complaints from the compiler cuz of the constant. So go back to our GetItemInput, our struct here. We're gonna put TableName as aws.String(TABLE NAME), okay? This is one of the important products to identify, like, okay, what table we're actually hitting. And I'm gonna have a key.

So key is going to be a map. The first value is a string, and it's going to map to our DynamoDB attribute value. And this attribute value is basically the definition of the records that we wanna check if they exist in our DynamoDB. And you can use attribute value for a bunch of different things.

You can have it for multiple fields, for this purpose, all we're checking is by the main primary key, which is our username, which is a string. So here, we put username, okay? And it's gonna be struct 2S. This S is specific for AWS. This represents that the field is a string, okay?

And it's different values, you can have like a float, an int, a Boolean, and all those, each of them have a separate character associated to them. So here we have username, I'm gonna put, aws.String(username). Okay? So this is basically the operation, it's gonna tell us, hey, do we have a record in our DynamoDB table, where the main key is username and the value is what we're passing in, which is string username, okay?

>> Can you repeat, how did you define table name again? Is it just a const at the top?
>> Yeah, yeah. So, the table name over here is just a const at the top, just user table. The documentation, they have this as a field on the DynamoDBClient struct, totally fine.

That's absolutely that makes sense. I'm just choosing just to put const here, just, I don't know, makes it more simplistic for me. Yes, question.
>> A couple of questions about the address versus pointer. Yeah, GetItem parameter for the DynamoDB.
>> Yep.
>> Why you are using that.
>> Yep, if I remove this, I'm gonna get a giant compilation error.

So I'm forced to use this. And this is by AWS design, because AWS, this is one of the properties of having references, you have the ability to use nil. And AWS has probably some logic under the hood that is using this know property to be able to handle some logic.

But also, when you pass a reference, it's more efficient than passing by copy. So if, imagine, our key here is it's dumb simple to one string. If you pass it by reference or copy, nothing's going to move. But imagine where we're inserting a humongous amount properties, we don't want to copy that through and through, it's not gonna be as performant.

So, passing by reference is gonna give us a little bit more performance to our execution here. And then, there's the nil ability as well, to handle nil correctly. Not correctly, but you can handle nil however you want. Okay, so now, most famous line in Go, if error does not equal to no, we're going to search.

So if the user exists, if there's an unforeseen error, we don't want to return false. And the reason for that, even if this user does not exist, if we experienced an error, let's say our DyamoDB table is down or something. We don't want to let our user know in the front of our logic to get mixed up and give them any kind of possibility to insert a user.

And you can check this multiple ways, so really this first argument it's, you don't really need cuz you can put false. I like to just be a little extra safe and put false, and in here what's actually required is this error. Because once we call this function, if there is an error, we will then catch that error in the layer above, in the API layer, okay?

Now, if result.Item is nil, this is one of those properties of why we're using reference and why AWS chose to use references, cuz we can do this no property, this zero value comparison. What this means is, does the user exist? No, there is no item here. So we can return false And nil.

Okay? Otherwise, there is an item, so we're gonna return true and nil. So there's three different categories, error, they exist, they don't exist. Cool? Anyone got any questions on that? Okay. Yes.
>> You have multiple return statements in there, like, other languages, when you run that first return statement, no further code execution [INAUDIBLE].

>> Yes. Exactly [INAUDIBLE]
>> Yep, that's correct. Yeah, so if we hit this block line 40, we'll not execute the rest of the code here. That's what we actually have this return here to kind of go have our function and to return some value if we never hit these if conditionals.

All right, so, that basically takes care of the function where we are checking, does a user exist? Now let's actually insert the user. So, given the exact same thing, let's go and ahead and make a receiver to our DynamoDBClient, and let's just call it InsertUser. And now this is where we're gonna use the very first file we modified, the user, which is of types.RegisterUser.

You can bring this in up here, lambda-func/types. That was the very first file we modified here, okay? And so, now we can return, basically the only thing we wanna return from this is an error, okay? Because we're choosing to separate our function and we have this earlier DoesUserExist, we don't need anything returned from this, just, it is an error, yes, no?

Okay, now, inserting a record into DynamoDB is not just like executing raw SQL or using ORM. You basically have to assemble your record in a type that DynamoDB understands, to be put into the database. Okay, so we first have to assemble the item and then we insert it.

So let's go ahead and assemble this. We can do item is going to be a reference to dynamodb.PutItemInput, okay? Again, reference versus non-reference, this is just how AWS SDK chose to organize their code, okay? We're gonna put TableName: aws.String, we're gonna put our const as table name. And the exact same thing we're gonna assemble, not our key, but our item, okay?

Because we wanna basically insert something into this. But it's gonna follow the same structure as a string, okay, to dynamodb.AttributeValue. And this struct is gonna basically contain all the records we wanna insert. So first is gonna be a username, okay, which as we've already wrote this aws.String, and it can be user.username, okay, because we're passing in our registered user and it has those fields associated to it.

And here we can do password and still gonna be sharing aws.String(user.password). Now, I know what you're thinking, this is the plain text password, it is. As of right now it, is the plain text password. So once we call this, we are literally going to be storing the actual password you put it as a user into our database.

It's not a permanent solution, we're gonna fix this. But all we've done so far is assemble the item, we actually haven't inserted the item at all. And to insert an item, we're gonna actually have to bring in a new, well, we don't actually have to bring anything, what we do have to do though is just call PutItem.

And this PutItem just takes the item that we just assembled above. The reason why we're ignoring the first argument is because that is a return that you can specify to return the user that you've inserted or return a property of that user once you've put it in the database, okay?

And again, you're gonna have to get used to if error does not equal to null. We could write a helper function, but that's not fun. Here, and then we can return null. Cool, and so we've kind of just assembled our logic for handling our DB connectivity, our DB experience.

Check if the user exists and then entering the user into that DB table.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now