Skip to content

TakumaKira/MarkdownEditor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Frontend Mentor - In-browser markdown editor solution

MarkdownEditor

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.

Table of contents

Overview

The challenge

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

Screenshot

Links

  • 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.

My process

Built with

  • 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

What I learned

  • 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.

Continued development

  • 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.

Useful resources

Author

How to run my solution

If you want to deploy this to Google Cloud Platform, please see How to deploy this to GCP.

Please give .sh files permission

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.

Run using kubernetes

Prerequisites for running kubernetes

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.

Prepare secrets for kubernetes containers

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.

Run everything on kubernetes

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).

How I prepared kubernetes manifest files

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, expects db-secret and api-secret(mentioned above) are generated adequately beforehand and uses database initialization scripts through ConfigMap.
  • Added spec.type: loadbalancer to all *-service.yaml files to allow access to containers.
  • Some renames.

Run using docker

Prerequisites for running docker

You just need to install Docker

Prepare env file storing secrets

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.

Run everything on docker

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.

Run for development

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.

Run frontend standalone

Please see README for frontend

Run API server standalone

Please see README for api.

Run database standalone

Please see README for db.

Run E2E testing

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>