Building Glue Stack From an Empty Folder
This covers how to create an application that manages users from a blank folder. It won't be kept quite as up to date as the Development Process Document because its a lot longer. It was interesting to go through and rewrite the application I just wrote and clarify some of the stranger parts. I think it would be really cool to show new team members how to build the core framework of the existing application from scratch but the cost might outweigh the benefit.
Create a file to write down any questions you have or anything that's unclear in this document so that we can improve it to make it easier for the next person.
Run the
setup.shas part of the Running Locally process. This will install some of the tools we're going to need.Make a development folder in your home folder. This is for all your development stuff.
Open terminal.
Command + Space(Spotlight) then type terminal.It will open your home folder by default, indicated by the
~(tilde).cd ~ mkdir development cd development
Create a folder for our app.
mkdir glue-stack cd glue-stackInitialise the folder as a git repository. Git is a tool for
version controlwhich tracks changes to files.git init
Database
Connect to MySQL
We're going to download MySQL and run it using a tool called Docker Compose which uses Docker then connect to it using Sequel Pro.
Create a docker compose file.
Use vim to create a
docker-compose.ymlfile.Hit
ito insert text.Paste the following:
Finish inserting by hitting
Esc.Save and quit using
:wq(colon then w then q, not all at once).The standard port for MySQL is 3306 but I've chosen to map it to 3307 so it doesn't clash if you already have a MySQL database running - you can't have two applications listening to the same port.
Start the database.
The MySQL database will be open after the line
Starting MySQL 5.7.21-1.1.3. To close it hitControl + C.Commit your changes to git from terminal.
Stage all your files (will just be our docker-compose file).
Commit.
This will bring up a vim editor just like we did before. To write your commit message you use
ito insert, write your commit message "added docker-compose file for database" above the commented (hashed) lines, thenesc, then:wq.
Download and install Sequel Pro from https://www.sequelpro.com/.
Open Sequel Pro (possibly using spotlight).
Connect to the MySQL database.
Click test connection.
If it succeeds click "Add to Favourites".
Click "Connect".
Select our "glue" database using the dropdown on the top left.
Create Tables
Organisation Table
Click the plus on the bottom left.
Name: organisation (its considered good practice to make sure table names aren't plurals; they're singular)
Table Encoding: UTF-8 Unicode (utf8mb4) (Unicode let's you store characters from all different languages)
Table Collation: Default (utf8mb4_general_ci)
Click Add.
That should bring up the Structure tab where you can manage the Fields of the table.
Click the plus icon in the middle of the page underneath the lone id Field to add a new field.
Add another Field.
Add another Field.
Add another Field.
Cool you just created the organisation table! If you right click on the organisation table then go "Copy Create Table Syntax" it should look like this:
Alternatively, you can click the console button on the top right to see the SQL statements Sequel Pro has ran then you can filter for "create" and "alter table" statements. This is useful if you've modified an existing table.
User Table
Each user belongs to a single organisation. We call this a Many-To-One relationship from a user to an organisation (or a One-To-Many relationship from an organisation to users). We model this relationship in relational databases using a foreign key. I like to think of relationships as associations as in a user is associated with an organisation and an organisation has users associated with it. The user table also has unique email addresses - no two users can have the same email.
Click the plus on the bottom left again, name the table
taskwith utf8mb4 again.Add the organisation field.
The type of this column is going to match the type of the id of the organisation table. To keep things consistent all id columns are the same type.
Add a foreign key.
A foreign key is a relational database constraint that means a particular field must match another field. In this case we're making sure the organisationId references an existing organisation.
Click the Relations tab (for the user table).
Click the plus icon.
Click Add.
Add another Field.
Add a unique index to the email field.
This makes sure that two users can't have the same email address.
On the structure tab, click the plus at the very bottom of the indexes panel.
Click Add.
Add another Field.
Add another Field.
Add another Field.
Add another Field.
Let's add the active, createdDate, modifiedDate fields but since we did the same thing for the organisation table we can click the console icon, filter for "alter table" statements and copy the ones for those fields. Open up the query tab and paste them in there and modify them for the user table.
Highlight them all then hit Command+r to run them.
That's it! Have a play with the content tab on the tables to enter data if you're keen.
API
Get the API Running
Install the Spring Boot CLI source.
Init a new spring boot project source.
Spring Actuator lets you access metrics about how the application is running
Spring Data JPA is a convenient way to access data
Spring Boot Development Tools gives us automatic reloads
Flyway keeps the database in sync with the application by managing SQL update scripts
MySQL is the Java driver for the MySQL database
Spring Security is for securing endpoints by requiring authentication
Web is for web applications and includes Tomcat and Spring MVC
You can find a full list of possible dependencies by running
Open the glue-stack folder.
Add some configuration to our project.
Hit Command+p then start typing
application.propertiesthen open it.This is where we configure our Spring Boot application.
Add the following:
Save using
Command + s.
Hit
Control + ~to bring up a terminal.Run the app for the first time!
It should finish with
Started ApiApplication in x seconds.You don't have to remember the command every time because you can use Control+r to search for previous commands. So you can hit Control+r then type spring to search for commands containing spring. You can then use Control+r again to search further back or Control+s search forwards.
Open Chrome and go to http://localhost:8080/.
Notice how everything is secure by default.
Commit your changes using the second menu item on the left. Just like before you can stage all or individual files and write a commit message saying "first running spring boot commit".
Configure Spring Security.
Open
ApiApplication.java(use Command+p).In that folder create a folder called config. This will be a package in Java.
Create a file in config called
WebSecurityConfiguration.java.The @Configuration annotation means spring will use this to configure the application.
This configures Spring Security to allow all request to all endpoints.
Save using
Command + s.Go to http://localhost:8080/actuator/health and it should say say "status: up".
This is an Actuator endpoint.
Add Database Scripts
Create a file called
V1__create.sqlatapi/src/main/resources/db/migration(you'll need to create a db and migration folder).Go back to Sequel Pro.
Right click the organisation tables, click "Copy Create Table Syntax" then paste it into the file.
Do the same for the user table then the task table.
Its important to do it in this order because the task table depends on the user table which depends on the organisation table.
Save using
Command + s.Delete the task table then the user table the organisation table.
Again, the order is important because of the dependencies.
Hit Control+c in the terminal where the API is running to stop it.
Open up the
application.propertiesfile.Remove the comment and replace the last line with.
Save using
Command + s.Start the API again by hitting the arrow keys up and down to find the command you previously used to start it.
Go back to Sequel Pro and hit the refresh button on the bottom of the left pain to refresh the tables.
You should see all your tables re-appaear plus a new table called
flyway_schema_history. That table is what Flyway uses internally to check where your database is at so it can run scripts on startup so that everyone's databases is kept up to date.Commit your work saying "added database setup scripts".
Create JPA (Hibernate) Entities
So we've now defined our database tables and their relationships to one another but we still need to create Java classes to represent that data so that we can use them effectively in our Java application. These Java classes are called Entities.
JPA (Java Persistence API) is a specification for Java ORM (Object Relational Mapping). A specification is like a Java interface where you define how you interact with the API without implementing it. ORM is all about mapping relational data in our database to Object Orientated data in our application so the data is more convenient to navigate. Some people really don't like ORM and I can definitely relate but I believe it's a useful concept to know. Hibernate is our JPA implementation that makes the JPA specification work and was around before JPA so that's why some sources will talk about hibernate without mentioning JPA.
Earlier, we identified some columns were the same in all the tables; id, active, createdDate, modifiedDate. We can also see that both the user and task table have an organisationId. Let me introduce you to the first programming concept in this tutorial; DRY - don't repeat yourself. This means try not to duplicate (copy and paste) code. Some people are pretty good at identifying common code in their head but i've always found it easiest to write duplicated code first so I can easily identify the common parts then abstract them. Abstraction means to reduce duplication.
I'm going to walk you through making the abstractions first becuase it makes this tutorial easier but I encourage you to write each of the classes in full then have a go abstracting second because I that's how I would develop outside this tutorial.
Create a file called
BaseEntity.javaatapi/src/main/java/org/gluestack/api/domain/entity(create the domain and entity folders (packages)).Cool, what we've done is declare the id, active, createdDate and modifiedDate as
fieldsand annotated them with some extra information for Hibernate. In Java the convention is to use getters and setters for each field instead of making them public so underneath the fields start writing set and get for each field and then you can use the autocomplete result. This is easier in other editors and I won't repeat this process for the rest of the tutorial.Create a
BaseOrganisedEntity.javain the same folder and generate the getter and setter for the organisation field. Don't generate the getter and setter for the superclass (BaseEntity) fields. Don't worry about compilation errors until after we've created all our entities because they all refer to each other.Create
Organisation.java. This is largely what you would call aPOJOin Java - Plain Old Java Object. APOJOis Java class that just has fields and getters and setters. We've added some annotations but I still largely consider this aPOJO.Create
User.java. This one is a little bit more involved because it's the implementation for our user for Spring Security.Start the application. Hibernate will validate that your data classes match your database and will fail to start if they don't.
Commit your work; "created entities".
Create the Repository Layer
The repository layer in Spring is a layer dedicated for communicating with data sources.
To do this, we'll need to add some dependencies to our project. Dependencies are dependencies on libraries. A Library is a separate, reusable piece of code that does something and makes your life easier and associated with the concept of abstraction which means to reduce complexity. A lot of people write Java Web Applications so its only natural that we deal with the same problems so libraries are an attempt to solve some of those common problems. In Java you used to have to download a compiled library but this is hard to maintain and leads to large project sizes so we use what's called dependency management where you simply specify the library and the version that you want and that will be used to download that library for you. In Java you can use Maven to manage dependencies and it stores the version information in a pom.xml file.
Add Querydsl JPA as a dependency. Querydsl JPA is a Java library for typesafe JPA queries that also works really well with another one of our cool libraries, Spring-Data-JPA, which is powering our repository layer.
Open the pom.xml file (using command + p) and add the following line to the properties element.
Add the following dependency below Flyway.
Add the following plugin to the plugins element below the spring-boot-maven-plugin.
Add this plugin so VSCode finds the classes that Querydsl generates.
Create the
BaseRepository.javafile atapi/src/main/java/org/gluestack/api/repository.Create the
OrganisationRepository.java.Create the
UserRepository.java. The findOneByEmail method will allow us to find a User in the database by their email and the EntityGraph annotation will load their Organisation in the same query. This will be useful for authentication. This is where Spring Data JPA is pretty cool, its an interface that automagically implements itself!Restart the application to make sure its all correct.
Commit your work; "created repository layer".
Create the Service Layer
The service layer is where most of your API logic lives. Some people choose to split up the service layer further where they see fit but I prefer Services call other Services as required instead of creating mandatory layers.
Create the
BaseService.javafile atapi/src/main/java/org/gluestack/api/service. Its a lot of custom code that I've developed for this project so just copy and paste it from this repo; BaseService.java. You may have to update the package and some of the imports so it compiles.Create the
OrganisationService.javafile in the same folder.This demonstrates the general structure of the services:
There's a method that returns a Querydsl Predicate which is a typesafe database criteria that has defines what entities can be returned from the database. You can see that this will only match the organisation of the current user and this is repeated for the other services as well.
The create method for an organisation also needs to create a user which this service is not responsible for so it uses a method on the UserService.
Create the
UserService.java.In this Service it overrides one of the save lifecycle methods to implement some extra functionality; hashing a user's plaintext password. The hashing functionality is provided by Spring Security and is important for keeping passwords safe in the database because it is a one way hash, it can't be decrypted. The API validates user's passwords by applying the hash to the password supplied during authentication and is checked against the hash in the database. The only way to 'decrypt' the hash is to generate hashes which should take a long time. That's why new hashing algorithms are created to stay ahead of the speed improvements of computers.
Java has a convention of
Mappingwhich might help here but I don't like the pattern much so i'm trying to avoid it and I think this code is okay.As always, start the appliation to make sure it works then commit your changes.
Create the Controller layer
The controller layer is what connects our application to the internet. It defines what URLs the application listens to and what Java objects we're expecting to receive or send. Spring MVC will convert the Java objects to and from JSON which is convenient for the frontend.
Create the
BaseController.javaatapi/src/main/java/org/gluestack/api/controller. Again, like the theBaseServiceits custom code so copy and paste it; BaseController.java.Create the
OrganisationController.java. This controller is a bit unique because it needs to handle new signups which means it works without a logged in user.Create the
UserController.java. This is what our typical controller method looks like, I would have liked to move the findAll method to the BaseController but currently its not possible.Create the
AuthenticationController.java. This is what the frontend will call to login and if authentication is successful it will return the details of the current user.Run the application and commit your work.
Final Configuration
Add the dependency for Jackson Hibernate Module inside the dependencies element in your
pom.xmlfile.In Java web applications there is the concept of mapping where you convert your Entities into other Java objects which the API would return. Instead of doing that I prefer to use my Entities for the output but Jackson, the library that spring uses to convert Java objects to and from JSON (serialisation and deserialisation), would try and serialise every relation of each entitity (and their relations and so on and so on), causing an infinite loop. This library prevents this by making Jackson only serialise the entities that have been loaded from the database. That means if you want to return more data all you have to do is load more data from the database, which you would have to do anyway if you mapped entities.
Create
WebMvcConfig.javainside the config folder.This will enable the Jackson Hibernate Module and the Spring Data Web Support which converts URL parameters into a Querydsl predicate to support filtering.
Create the
UserDetailsService.java. This will be used to configure Spring Security and will be used as part of authentication.Open up the
WebSecurityConfiguration.javafile. Make it look like this:Start the application and commit your work.
Tests
I'm not the best at tests but I think I like this approach.
Add the dependency on Testcontainers in the
pom.xml.Testcontainers allow us use a real MySQL database for our tests and its loaded using docker so you have to have that installed but luckily that's part of our setup scripts.
Create an
application-test.propertiesatapi/src/main/resources/.This will specify to connect to the testcontainers database during testing. You don't need to replace anything in this code snippet.
Delete the
ApiApplication.javafile fromapi/src/test/java/org/gluestack/api.In that same directory, create the
BaseTest.java.My current testing strategy is to have a decent amount of test data available so that you don't have to setup a lot of data for new tests. This might not scale in the long run so I may have to break it up a bit but I think the concept of making it really easy to setup data or not having to do it at all is a good.
The other important thing here is the Transactional annotation. That each test will run in a separate database transaction and will be rolled-back after the test completes. This means the database will be in the same state before each tests regardless of which tests ran before it, making our tests independent.
In that same folder create
TestOrganisation.java. This is the object to pass around test data.Create
TestOrganisationService.java. This creates the test data.Open
BaseTest.javaand add the following to the bottom of the class. This will setup two organisations and their data so that we can reference it for our tests.Create a
POJOfor thePageResponsethe list views return. I'm not 100% sure why we have to do this but its not terribly hard. Copy it from https://github.com/cadbox1/glue-stack/blob/master/api/src/test/java/com/api/PageResponse.javaCreate
CreateTest.javaatapi/src/test/java/org/gluestack/api/organisationCreate
FindAllTest.javaatapi/src/test/java/org/gluestack/api/userAt the command line, inside the api folder, run this command to run all the tests.
Commit your work.
UI
Create React App
Open a new terminal in VSCode and run this command.
This will install
create-react-appglobally.create-react-appis by far the most popular way to get aReactproject started.Create a new project by running the command then the name of the project.
Follow the commands it displays in the terminal.
That should open a working react app in your browser, how easy was that!
Add this line before the dependencies.
This will route any unkown urls to our API.
Stop the front end development server using
Control + c.Run yarn. This will produce a new lockfile.
Yarn is an alternative package manager to npm that uses a lockfile that records the exact version of each dependency that is installed. The lockfile differs from that
package.jsonwhich stores the ranges of versions that are to be installed or updated to. e.g do you want to jump major versions or just minor versions. The lockfile means more consistent results across different environments.Commit your work.
create a
.envfile atui/.This will allow us to import the folders inside the src folder absolutely instead of relatively which is more robust when moving stuff around. Its always a good idea to link any relative issues in code that help you come to a solution.
Create a
.prettierrc.yamlfile.This will specify how we want the prettier to format our code, including the prettier VSCode plugin we installed.
Create a
tsconfig.jsonfile.This will let VSCode understand our project. Tt will make the autocompletes, called Intellisense in VSCode, a lot more helpful.
Commit your changes.
Add Libraries
We're going to add some Libraries to our project mainly in the form of dependencies. Just like in Java we're going to have a file that holds our library names and versions so that we can download them all instead of putting them in our project. When we run yarn add it will download the library from NPM and add it to our package.json.
Open a terminal window and cd into the ui directory.
React Router (and friends). This is for Routing which is about mapping the url in the address bar to different pages in our app.
Axios is a small library that makes netwrok requests just that much easier.
Material UI Beta. Material UI is the most popular material design library for
React. This library is going to heavily influence the design of our app using the popular Material Design specifications. Their previous version had some pretty big limitations once you got into it but they've learned a lot and delivered a fantastic beta version. Being a Beta they tend toMove Fast and Break Things(and that's a good thing) so we're going to use the exact version I know works.Bootstrap. Okay, we definitely shouldn't need this but I know the utilities fairly well so it helped me get the ui working quickly but I will definitely be removing this eventually.
React Component Queries allows us to detect the sizes of components so that we can keep our app responsive and working nicely on mobile devices.
Okay this next step is a bit of a cop out. Its the re-usable code I've developed while creating the frontend. I really should be explaining each peice but I think i'll come back and do that later. Sorry!
Copy the common folder to
ui/src/.Start the frontend server. This will automatically open the app in a browser window.
Setup React Router and Material UI at the root component. Open
App.js.The browser window should automatically refresh and display the word "App".
Delete the
logo.svg.Open
App.css.Commit your work.
API Requests
Create a folder called
apiatui/src/.Create the
organisation.jsfile.Create the
user.jsfile.Create the
task.jsfile.Create the
authentication.jsfile.Commit your work.
Authentication Component
Create a folder called
mainatui/src/.Create the
index.jsfile atui/src/main.Open
App.js.Delete the
<p>App</p>line and start typing the word<Main. Then hit enter on the first autocomplete result that says "import main". This will automatically add the import for theMaincomponent.Close the
Maincomponent tag with/>so it says<Main />.The browser window should now show "Main".
Commit your work.
Login Component
Create the
index.jsfile atui/src/main/unauthenticated/login.class Login extends Component { constructor(props) { super(props); this.state = { email: "", password: "" }; }
}
Login = withRouter(Login);
export { Login };
```
Create the
index.jsatui/src/main/unauthenticated.Open the
main/index.jsby hitting Command + p then start typingmainthen type/.Add this between the start of the render function and the return.
So it looks like this.
Put yor cursor at the end of
Unauthenticatedword then hit Control + space to bring up the autocomplete. Select the option to import the Unauthenticated component, it should be the second option.The browser should now be showing a login form.
Signup Component
Notice how our Signup Here link doesn't work. Let's fix that.
Create the
index.jsfile atui/src/main/unauthenticated/signup.Open
unauthenticated/index.jsusingCommand + p.Before the
<Route>, create a route for the signup component.Again, use autocomplete to import the signup component. I'm not going to say this anymore you can just do it for new stuff.
The signup link should now work as well as the login here link on the signup page.
First Signup
Start the API and Database if they are not already running.
In separate tabs in VSCode.
Database.
API.
Fill in the signup form and click signup. You should now see the word "Main" which means you are logged in.
Sidebar
Create
index.jsatui/src/main/authenticated/sidebar.Create a
drawer.jsfile in the same folder.Create
index.jsatui/src/main/authenticated.Open
main/index.jsand replacereturn "Main"in the render function with:You should now be able to logout, toggle the sidebar and see how it behaves differently on smaller screens. None of the links change anything but we'll fix that soon.
Commit your changes.
Users
Create the
list.jsfile atui/src/main/authenticated/user/.Create the
form.jsfile in the same folder.Create the
index.jsfile.Replace the button in the
authenticated/index.jsfile with these routes.Add the
react-routerimports and use the auto import for the User componentCommit your work.
Last updated