Dockerize Node.js TypeScript with Docker, MySQL and Compose
Posted January 25, 2024
Do you want to run your Node.js MySQL TypeScript apps with Docker and Docker Compose? This guide explains every step to getting a TypeScript Node.js app Dockerize and running as a complete Docker container.
Along this guide, you learn:
- An example application set up using TypeScript, Node.js and MySQL.
- How to create a Multi Build Dockerfile to Run TypeScript and Node.js on Docker.
- The right way to connect Node.js and TypeScript on Docker with MySQL using Docker Compose.
- How to access a MySQL container with Adminer.
- How to populate your MySQL database with sample data and use it on your Node.js TypeScript API.
Now ensure you have Docker installed and running on your computer.
Setting up TypeScript Node.js App with Docker
In this example tutorial, we won’t create a Node.js API from scratch. I will assume you have a working Express and MySQL TypeScript app working. If not, check this TypeScript NodeJS Server tutorial with MySQL2 and ExpressJS
First, clone this GitHub repository to get a working TypeScript NodeJS Server with MySQL2 and ExpressJS:
git clone kimkimani/TypeScript-NodeJS-MySQL2
You should have the API ready:
Ready? Let’s now dive in and learn how to Dockerize your TypeScript Node.js app with MySQL using a Docker container.
Preparing TypeScript with Docker and Node.js
To be able to run TypeScript on Docker, you will need to make sure the following changes are ready:
To build TypeScript, Docker will use build
as the outDir. This means you must have the following line in your tsconfig.json
file:
/* Add inside compilerOptions */
"outDir": "./build",
"rootDir": "./src",
You will need commands to run your TypeScript. Docker will use the same commands. In your package.json
file, ensure the following commands are ready:
"scripts": {
"build": "tsc",
"dev": "nodemon --watch src --exec 'ts-node' src/index.ts",
"prod": "node -r ts-node/register/transpile-only build/index.js"
},
Creating Dockerfile to Run Node.js and TypeScript on Docker
A Dockerfile uses a script to create a Docker image. It contains instructions for building a Node.js app and how to run TypeScript code with Docker.
In your root directory (based on your APP OR the one you have cloned above), create a new file and name it Dockerfile.
We will use Multi-stage to run Node.js and TypeScript with Docker.
A Multi-stage Dockerfile allows you to create different sets of instructions within the same Dockerfile and target TypeScript in development or production mode. This way:
- You will use multiple FROM statements in a single Dockerfile
- create multiple intermediate images during the build process.
- Each stage is like a separate image in the build process.
- You will copy artifacts from one stage to another, keep the final image size smaller, and only include what is necessary for runtime.
- Have commands to run TypeScript in development and production mode on Docker
Now add the following instructions to this Dockerfile:
# .......Development Stage.......
FROM node:20-alpine as development
# Set the working directory in the container
WORKDIR /app
# Copy package.json and package-lock.json to the working directory
COPY package.json package-lock.json ./
# Upgrade npm to the latest version globally
RUN npm install -g npm@latest
# Install project dependencies
RUN npm install
# Install ts-node globally for running TypeScript code
RUN npm install -g ts-node
# Copy the entire application code into the container
COPY . .
# Build the application
RUN npm run build
# .......Production Stage.......
FROM node:20-alpine as production
# Define an argument for the Node environment
# with a default value of "production"
ARG NODE_ENV=production
# Set the environment variable for the Node environment
ENV NODE_ENV=${NODE_ENV}
# Set the working directory in the container
WORKDIR /app
# Copy package.json and package-lock.json to the working directory
COPY package.json package-lock.json ./
# Install only production dependencies
RUN npm install --only=production
# Copy the entire application code into the container
COPY . .
# Copy the build artifacts from the development stage to the production stage
COPY --from=development /app/build ./build
# Default command to run when the container starts in production mode
CMD npm run prod
In this example:
- Docker will pull and use Node.js 20 on Alpine Linux as the base image.
- It will copy your TypeScript code to Docker on
app
as the working directory. - This way, Docker will install dependencies and build your app as such.
- Note that we have an
ENV NODE_ENV=${NODE_ENV}
.
The same Docker creates a multi-stage build for your TypeScript Node.js application based on development and production environments. In this case:
- Stage 1 (development) will set up the working directory, install dependencies, copy the application code, and build the application. This way, Docker will compile TypeScript to JavaScript within Docker in the working
- This first stage will allow you to run TypeScript and Node.js on development while on Docker. The stage builds and prepares the application.
- Stage 2 (production) creates a production environment.
- The
--from=development
command instructs Docker to copy the compiled TypeScript code, allowing a clean separation between development and production dependencies.
Creating TypeScript MySQL Database Scripts on Docker.
Up to now, you have the correct code needed to get Node.js and TypeScript running on Docker. Here, we are using MySQL as the database. If you are on Docker, you don’t need to manually create your database and table. We will use Docker Compose to do this.
Therefore, you will need SQL start scripts that MySQL will instruct Docker to use to initialize your database data. In your working directory, create a script
folder and add:
- Use the
schema.sql
file to create the database and the table. This example will use theblog
as the database andposts
as the table:
/* Create the database */
CREATE DATABASE IF NOT EXISTS blog;
/* Switch to the blog database */
USE blog;
/* Drop existing tables if they exist */
DROP TABLE IF EXISTS posts;
/* Create the tables */
CREATE TABLE posts(
/* Add auto-incrementing primary key */
id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
/* title column*/
title VARCHAR(255) NOT NULL,
/* description column of type TEXT */
description TEXT NOT NULL,
/* author column, allowing NULL values */
author VARCHAR(255) DEFAULT NULL,
/* content column */
content VARCHAR(255) DEFAULT NULL,
/* a created_at column with the TIMESTAMP data type
set to the current timestamp by default */
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Note that this SQL code must align with your application Model. If you use a different app, check your model/interface class and make sure the SQL code aligns with your structure.
data.sql
contains sample data that MySQL will tell Docker to insert in your database before running TypeScript code:
INSERT INTO posts (title, description, author, content)
VALUES
('Introduction to SQL', 'Learn the basics of SQL', 'John Doe', 'SQL is a powerful language for managing relational databases.'),
('Web Development Tips', 'Tips for building modern web applications', 'Jane Smith', 'Stay updated with the latest web development trends and best practices.'),
('Data Science with Python', 'Explore the world of data science using Python', 'Robert Johnson', 'Python is widely used in the field of data science for its versatility and rich ecosystem.'),
('Delicious Recipes', 'Discover new and tasty recipes', 'Alice Brown', 'Try these mouthwatering recipes at home and impress your friends and family.'),
('Travel Adventures', 'Explore the wonders of the world', 'Michael Wilson', 'Embark on exciting travel adventures and create lasting memories.'),
('Fitness Journey', 'Tips for a healthy and active lifestyle', 'Emily Davis', 'Stay fit and healthy with these workout routines and nutrition advice.');
Spinning up Node.js, TypeScript and MySQL with Docker Compose
Let’s now dive into Docker Compose and get these Docker Containers ready. Docker compose tells Docker to run a multi-container setup. Here you will have:
- A Node.js Docker container to run TypeScript
- MySQL service will get MySQL up and running on Docker.
- An Adminer container to access MySQL on a UI.
You only need one file to set up these three, a docker-compose.yml
. Docker Compose will use it to manage and deploy them as a multi-container Docker app and as a single unit.
In your Working directory, create a docker-compose.yml
file with the following Docker Compose instructions:
version: '3.8'
services:
mysql_server:
image: mysql:8.1.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: blog
MYSQL_USER: db-user
MYSQL_PASSWORD: testpass
ports:
- 3306:3306
volumes:
- mysql-data:/var/lib/mysql
- "./script/schema.sql:/docker-entrypoint-initdb.d/1.sql"
- "./script/data.sql:/docker-entrypoint-initdb.d/2.sql"
networks:
- app-network
adminer:
image: adminer
ports:
- 8080:8080
environment:
ADMINER_DEFAULT_SERVER: mysql_server
depends_on:
- mysql_server
networks:
- app-network
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/app
environment:
NODE_ENV: development
depends_on:
- mysql_server
command: npm run dev
ports:
- "3000:3000"
networks:
- app-network
volumes:
mysql-data:
networks:
adminer-network:
Let’s break down each service.
mysql_server
will run the MySQL server. In this case, it will expose 3306 and create a sample database, password, and username. At the same time, This service will execute thedata.sql
andschema.sql
files to create your table and insert data.app
will execute the Dockerfile and set Node.js and TypeScript with Docker. In this section, you will target thedevelopment
stage and run the commandnpm run dev
to start the server. Also, note the./:/app
volume. I will show you its magic later.- To access MySQL, you will use
adminer
. You can check this Easy Apache Guide with Docker Compose, MySQL, and PhpMyAdmin guide if you want to use PhpMyAdmin instead.
Connecting MySQL and Node.js TypeScript DB connection from Docker
Now, you need to edit your TypeScript Node.js code and reflect its connection to the above MySQL variables. In this example, you will go to database.ts
and add the following changes:
import { createPool } from 'mysql2/promise';
export async function connect() {
const connection = await createPool({
host: 'mysql_server',
user: 'db-user',
password: 'testpass',
database: 'blog',
connectionLimit: 10
});
return connection;
}
Note that TypeScript will use these MySQL connection variables based on how you have defined MySQL service with Docker in your docker-compose.yml
file.
The Key point here is the host
. It should not be localhost. The host name here is mysql_server
, which is the Docker Compose service responsible for running MySQL.
If you are accessing these elements using a .env
file or dotenv as follows:
import { createPool } from 'mysql2/promise';
export async function connect() {
const connection = await createPool({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE,
port: Number(process.env.MYSQL_PORT),
connectionLimit: Number(process.env.MYSQL_CONNECTION_LIMIT)
});
return connection;
}
You only need to include these variables in your docker-compose.yml
file. However, you must add them to the app
service in the environment
section as follows:
app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./:/app
environment:
NODE_ENV: development
MYSQL_HOST: mysql_server
MYSQL_DATABASE: blog
MYSQL_USER: db-user
MYSQL_PASSWORD: testpass
MYSQL_PORT: 3306
MYSQL_CONNECTION_LIMIT: 10
depends_on:
- mysql_server
command: npm run dev
ports:
- "3000:3000"
networks:
- app-network
Running the TypeScript Node.js Container
Every setup should be ready. Use the following command to spin up the containers.
docker-compose up --build -d
This command will build the TypeScript Node.js and MySQL container and run them on Docker:
Open Docker and ensure these services are up and running as follows:
To confirm the TypeScript code is running on Docker, click the app service:
Now, you will access these logs and know if Nodemon is running the TypeScript (on Docker) as expected:
Remember the - ./:/app
volume you created in your Docker Compose file? Here is magic. Now if you change the code on your local machine, Docker will be able to pick up the changes and use Nodemon to rerun the new code. Note that this time you don’t have to rerun the docker-compose up --build -d
command to get these TypeScript changes on Docker. Once you save the changes, Nodemon and Docker will work the magic:
Testing the Dockerize Typescript Node.js App
You have successfully built a TypeScript Node.js container. To test it, open Postman and send a GET request. This should fetch the first data you added using the data.sql
file.
Send a GET request to http://localhost:3000/posts
:
To add data, Use the POST request as follows:
Go further and test DELETE AND PUT.
Accessing MySQL with Adminer
We have Adminer running on http://localhost:8080/
. Open it on the browser:
Here:
- Select the MySQL system.
- The server should be a
mysql_server
service. - Add username
db-user
- Add the database user password
testpass
- Use the
blog
database.
Once you log in, you will have the posts table, just as Docker created it:
Open this table and see your data right away:
Conclusion
This guide taught you how to create a Multi Build Dockerfile to Run TypeScript and Node.js on Docker. You now have the right way to connect Node.js and TypeScript on Docker with MySQL using Docker Compose. At the same time, access a MySQL container with Adminer. Check all the TypeScript apps and Docker codes on this GitHub repo.