In this blog, I write about Web Development, Blockchain, and DevOps. I am a Docker Captain, Public Speaker, and Developer Advocate at daily.dev. Check my YouTube channel.
spring.datasource.username: the username of the database.
spring.datasource.password: the password of the database.
spring.jpa.hibernate.ddl-auto: the way we want to update the database. We will use update to create the tables if they don't exist, and update them if they do.
spring.jpa.properties.hibernate.dialect: the dialect of the database. We will use PostgreSQL.
We will use the environment variables later (and it will be a bit tricky).
π Create the resource structure
Create a new folder called users in the demo folder (or whatever you named your project).
Create three files in this folder:
User.kt
UserRepository.kt
UserController.kt
Your folder shold look like this:
Now let's populate the files.
User.kt
The User.kt file will contain the entity of the user.
Open the file and add the following content (change the package name to match your project):
package com.example.demo.users
import jakarta.persistence.*
@Entity@Table(name = "users")dataclassUser(
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)val id: Long,
val name: String,
val email: String
)
Explanation:
@Entity: decorator to tell Hibernate that this class is an entity.
@Table: decorator to tell Hibernate the name of the table in the database ("users in this case").
@Id: decorator to tell Hibernate that this field is the primary key.
@GeneratedValue: decorator to auto-increment the id whenever we create a new user.
An user will have three fields: id, name and email.
UserRepository.kt
The UserRepository.kt file will contain the interface to interact with the database.
Open the file UserRepository.kt and add the following content (change the package name if you used a different one):
interface UserRepository: the interface that will contain the methods to interact with the database. It will be of type CrudRepository. This is a generic interface that contains the basic methods to interact with the database. It will have a type User and an Int (the type of the primary key).
UserController.kt
The UserController.kt file will contain the Rest API.
Open the file UserController.kt and add the following content (change the package name if you used a different one):
@RequestMapping: to tell Spring the base url of the Rest API. In this case, it will be /api/users.
@Autowired: to tell Spring to inject the UserRepository.
Then we have the five methods to interact with the database:
getAllUsers: to get all the users.
createUser: to create a new user.
getUserById: to get a user by id.
updateUserById: to update a user by id.
deleteUserById: to delete a user by id.
Our Rest API is ready to get Dockerized.
π³ Dockerization
Now the fun part: Dockerization.
In this project, I decided to build the Kotlin project directly inside the Docker image.
Another option would be to build the project locally and then copy the jar file to the Docker image.
π Dockerfile
Create a new file called Dockerfile in the root of the project.
Add the following content (explanation is in the comments):
# Start with a base image containing Java runtime
FROM amazoncorretto:17-alpine-jdk
# Create a directory
WORKDIR /app
# Copy all the files from the current directory to the image
COPY . .
# build the project avoiding tests
RUN ./gradlew clean build -x test
# Expose port 8080
EXPOSE 8080
# Run the jar file
CMD ["java", "-jar", "./build/libs/demo-0.0.1-SNAPSHOT.jar"]
β οΈ The unusual part here are the ARG lines. They are used to pass arguments to the Docker image. They are defined in the docker-compose.yml file.
π docker-compose.yml
Let's create the docker-compose.yml file at the root of the project.
Add the following content (explanation is in the comments):
version:'3.9'services:kotlinapp:container_name:kotlinappbuild:# this is the build context: . context:.dockerfile:Dockerfileargs:# these are the arguments that are passed to the dockerfileDB_URL:${DB_URL}PG_USER:${PG_USER}PG_PASSWORD:${PG_PASSWORD}ports:# port exposed to the host machine-"8080:8080"environment:# these are the environment variables that are passed to the dockerfileDB_URL:jdbc:postgresql://db:5432/postgresPG_USER:postgresPG_PASSWORD:postgresdepends_on:# this is the dependency on the db service-dbdb:container_name:dbimage:postgres:12environment:# environment variables for the Postgres containerPOSTGRES_USER:postgresPOSTGRES_PASSWORD:postgresPOSTGRES_DB:postgresports:# port exposed to the host machine-"5432:5432"volumes:# volume used to persist data-pgdata:/var/lib/postgresql/datavolumes:# volume creationpgdata: {}
Build and run the project
Now we can build and run the project.
π½ Run the Postgres database
First, we need to run the Postgres database.
docker compose up -d db
To check if it's running, you can use the following command:
docker compose logs
and the
docker ps -a
If the output is like the following one, you are good to go:
You should see something like that, you are good to go.
As additional test, you can connect to the database using TablePlus (or any other database client).
You can create a new connection using the following parameters:
Host: localhost
Port: 5432
Database: postgres
User: postgres
Password: postgres
Then click on the Test Connection button. The database is connected but emptt for now.
ποΈ Build the project
Let's build the project inside the Docker image.
docker compose build
And the output should be something like that:
πββοΈ Run the project
Now we can run the project.
docker compose up kotlinapp
And this should be the output:
π§ͺ Test the project
Now we can test the project. We will use Postman, but you can use any other tool.
π Create a user
To create a new user, make a POST request to localhost:8080/api/users.
The body of the request should be like that:
{
"name": "aaa",
"email": "aaa@mail"
}
The output should be something like that:
Let's create two more users, make a POST request to localhost:8080/api/users.
{
"name": "bbb",
"email": "bbb@mail"
}
{
"name": "ccc",
"email": "ccc@mail"
}
π Get all users
To get all users, make a GET request to localhost:8000/api/users.
The output should be something like that:
π Get a user
To get a user, make a GET request to localhost:8000/api/users/{id}.
For example GET request to localhost:8000/api/users/1.
The output should be something like that:
π Update a user
To update a user, make a PUT request to localhost:8000/api/users/{id}.
For example PUT request to localhost:8000/api/users/2.