This is a full-stack solution to the In-browser markdown editor challenge on Frontend Mentor. Frontend Mentor challenges help you improve your coding skills by building realistic projects.
- Frontend Mentor - In-browser markdown editor solution
Users should be able to:
- Create, Read, Update, and Delete markdown documents
- Name and save documents to be accessed as needed
- Edit the markdown of a document and see the formatted preview of the content
- View a full-page preview of the formatted content
- View the optimal layout for the app depending on their device's screen size
- See hover states for all interactive elements on the page
- Bonus: If you're building a purely front-end project, use localStorage to save the current state in the browser that persists when the browser is refreshed
- Bonus: Build this project as a full-stack application
- Solution URL: Frontend Mentor
- Live Site URL: Vercel Signup/Login feature does not work here as I don't want to provide any backend services publicly.
- React - JS library
- React Native - React framework for building native mobile apps
- Expo - React Native framework
- Storybook - For Component Driven Development and visual testing
- Express - For building API server
- Jest - For unit testing
- MySQL - For building database
- Cypress - For E2E testing
- Docker - Containerized development environment
- Kubernetes - Manage deployment of containerized applications
- Skaffold - Kubernetes orchestration(only used to run entire app quickly)
- Google Cloud - Platform to deploy applications using Docker and Kubernetes
- I needed to chose frameworks carefully to realize the most suitable development environment for this project.
- Basically, I wanted to build this as web app, but also as mobile apps. So I saw Flutter, React Native and React Native using Expo. I wanted to experience React Native more for now, and whole setup things of pure React Native was not my current point. Besides, it was plus for this that there were more documents for using Storybook with Expo.
- This is the first time for me to build and deploy a full stack application, so I needed to (re)learn a lot about backend stacks like Express/MySQL and deployment stacks like Docker/Kubernetes/Google Cloud. Obviously I still need to learn much more about these areas, but this will be a great first step for me.
- I tried to using Chromatic for getting visual difference of the Storybook of each development, but it looked not to support React Native for now.
- Setting up Storybook for React Native/TypeScript - Setting up Storybook correctly was the most challenging part of the setup phase of this project. This explains how to see the stories with Expo project which other explanations left off(I needed to conditionally export
StorybookUIRoot
as App root). - Courses on Code with Mosh - He always shows us not "how to write working code" but "how to write clean code". As starter of building my first full stack application, courses like The Complete Node.js Course / Complete SQL Mastery / The Ultimate Docker Course helped me to get a right sense of new tools.
- A Better Approach to Google Cloud Continuous Deployment - This showed me a beautiful way to setup CI/CD environment on Google Cloud.
- Website - TakumaKira@Github
- Frontend Mentor - @TakumaKira
If you want to deploy this to Google Cloud Platform, please see How to deploy this to GCP.
When you try to run this project, you might encounter error messages like permission denied: ./some-shell-script.sh
, which I prepared mostly for initializing database. In such a case, please run chmod +x ./some-shell-script.sh
to give the permission it needs to run.
If you are willing to run this on local kubernetes, please make sure you installed Skaffold.(I'm not sure if the install takes care, but it definitely requires tools like kubectl, minikube and Docker to run kubernetes project on your machine. Skaffold just orchestrates multiple tasks on a single command as declaration files I prepared in /k8s-manifests
.)(trivia: I googled that '8' is 'ubernete' in Greek so 'kubernetes' often call 'k8s'...)
If you had installed and used them before, you may still need to start docker and then minikube with command like minikube start
.
When your machine are ready to run kubernetes projects, you need 2 steps to go to run this.
First, setting up secrets for api and db.
For db, please run below:
kubectl create secret generic db-secret \
--from-literal=MYSQL_ROOT_PASSWORD=<password-for-root-user-of-your-local-mysql-container> \
--from-literal=MYSQL_PASSWORD=<password-for-app-as-a-database-user>
And below is for api:
kubectl create secret generic api-secret \
--from-literal=JWT_SECRET_KEY=<secret-key-for-api-to-verify-json-web-tokens> \
--from-literal=MYSQL_PASSWORD=<password-for-app-as-a-database-user> \
--from-literal=STANDARD_MAIL_SERVER_HOST=<your-email-service-provider.com> \
--from-literal=STANDARD_MAIL_SERVER_USER=<your-email-user-name> \
--from-literal=STANDARD_MAIL_SERVER_PASS=<your-email-user-password>
These values will be provided to each container.
Then, just run skaffold dev --port-forward
and your terminal will tell you the addresses you can access(like http://localhost:4503
for frontend).
I used Cloud Code Extension for VSCode, which is really great for inspecting running kubernetes, but using Skaffold CLI was stable in my case, so I mostly use Cloud Code Extension for debugging(it can inspect even if you run it from Skaffold CLI).
This skaffold.yaml configuration includes only unit tests for frontend, so you need to run database initialization scripts tests manually whenever modified(See /db/README.md) and make sure including api testing step on your CI/CD(See How to deploy this to GCP).
I used kompose to generate the base of kubernetes configuration files from docker-compose.yaml
in this directory. If you want to try this step, follow the instruction below.
Run kompose convert
to generate yaml files to apply to Kubernetes.
If you check the diff between its result and what k8s-manifests
directory has, you can see some modifications I applied. I'm not going to explain every one of them, but point out what I wanted to do.
- Modified how to reference environment variables, which contains configurations and secrets. I modified it as it reads configurations from
api-configmap
, expectsdb-secret
andapi-secret
(mentioned above) are generated adequately beforehand and uses database initialization scripts throughConfigMap
. - Added
spec.type: loadbalancer
to all*-service.yaml
files to allow access to containers. - Some renames.
You just need to install Docker
docker-compose.yaml
will read *.env
files containing secrets. I prepared template files so you just fill them out with your own values. Please remove .template
from docker-compose-api-secrets.template.env
and docker-compose-db-secrets.template.env
(then these files will not be tracked by git as written in .gitignore
), and fill out the values inside to your own.
When you finished preparing docker-compose-api-secrets.env
and docker-compose-db-secrets.env
as mentioned above, then just run docker compose -p markdown up --build
and docker will build images from resources(this command will take a few minutes for the first build) and run everything work together.
If you want to make a change on this project, running every part in development setting would be fastest to make sure the change will work as a whole. Below are the commands for each part.
Please see README for frontend
Please see README for api.
Please see README for db.
When you get frontend/api/database all running and working with each other, then you can open up E2E testing tool with Cypress by running the following command:
When you run this for the first time, you need to run yarn install
first to install dependencies.
When you run E2E tests against remote url deployed on GCP, you need to connect to database using Cloud SQL Auth Proxy as tests needs to access directly to the database. You should be able to connect the database with host 0.0.0.0
and port 3306
using command like ./cloud-sql-proxy --address 0.0.0.0 --port 3306 <your-gcp-project-id>:<your-gcp-project-region>:<your-gcp-database-instance-name>
and set 0.0.0.0
as DATABASE_HOST
of commands below.
To open testing window, run the following command.
CYPRESS_BASE_URL=<frontend-url> \
CYPRESS_MAILOSAUR_API_KEY=<your-mailosaur-api-key> \
DATABASE_HOST=<your-database-host-ip> \
MYSQL_DATABASE=markdown_editor \
MYSQL_USER=markdown_editor_app \
MYSQL_PASSWORD=<your-password-for-app> \
API_JWT_SECRET_KEY=<your-api-jwt-secret-key> \
yarn cypress:open \
--env MAILOSAUR_SERVER_ID=<your-mailosaur-server-id>,API_BASE_URL=<your-api-base-url>
To run tests and record result on Cypress Cloud, run the following command.
CYPRESS_BASE_URL=<frontend-url> \
CYPRESS_MAILOSAUR_API_KEY=<your-mailosaur-api-key> \
DATABASE_HOST=<your-database-host-ip> \
MYSQL_DATABASE=markdown_editor \
MYSQL_USER=markdown_editor_app \
MYSQL_PASSWORD=<your-password-for-app> \
API_JWT_SECRET_KEY=<your-api-jwt-secret-key> \
yarn cypress:record \
--key <your-cypress-cloud-record-key>
--env MAILOSAUR_SERVER_ID=<your-mailosaur-server-id>,API_BASE_URL=<your-api-base-url>