Make a Markdown Blog using Amplication and ReactJS!
Mục Lục
Make a Markdown Blog using Amplication and ReactJS!
Project Introduction
The basic idea behind the project is to create a Markdown Blog Site. The final product will be a website in which users can write the article in markdown, automatically converting it into plain text.
Here is a sneak peek at what our project will look like –
Markdown
Markdown is a very popular markup language, used to format documents. It is widely used in discussion platforms, blog sites, emails, e-books, etc.
The most famous real-life example, for developers of use, of markdown is StackOverFlow and Github comments on PRs!!
Some basics of Markdown are below, just to give you an introduction about what it is?
# is used for ‘heading 1’ ## is used for ‘heading 2’ ### is used for ‘heading 3’
These are some basics of Markdown, if you want, you can further read the basics of it here.
Introduction to Amplication!!
Let’s say we want to build a website. We’d have to choose a language, a framework, a database, figure out how we want to deploy, build some models, create REST endpoints… and so on and so forth.
It turns out that now we can do all this in a matter of minutes, using a technology called Amplication. All it needs from us is a brief idea of the data models — or entities. It then generates a bunch of code to do all the aforementioned tasks and more.
How does this work?
Amplication creates a full-fledged application using metadata that you can define using the Amplication web console. The auto-generated application is based on Node.js + Typescript on the server-side and React on the client-side.
There is a great introductory blog written by the Amplication team: Introducing Amplication — the open-source development tool that I would recommend for reading alongside this article!
Technology
The code generated by Amplication is based on a number of popular technologies.
Framework- NestJs
Database- Postgres
Version Control- Github
Deployment- Docker
Runtime- Node.js
Let’s explore how these come together in the form of a functional, auto-generated backend. We’ll use the standard example of building a Markdown Blog Application.
Let’s dive in
Backend
The first step to create this blog application is to log in to Amplication and make an application with an entity Article in it.
This will make a backend application without doing repetitive coding tasks. You can read Amplication Docs in order to learn about the basic process.
The Article entity should have the following specifications in it:-
Title- Single-Line Text
Short Description- Multi-Line Text
Content- Multi-Line Text
We can also sync the Amplication code to Github. Now, we have to commit and build and generate the code.
This will create a pull request to the selected Github repository and we have to just merge it.
In the next steps to make the markdown blog, we will run the code, generated by amplication, locally.
So, just clone the code from the Github repository and open it in VSCode.
Change the directory to the server and then run some basic commands mentioned here.
You can also see the REST API created by Amplication by opening the sandbox environment. It will look like this-
This shows the server is running and all-ready to do the work!
I am just thinking if we don’t use Amplication in the backend process and have to do the coding part by ourselves then how long and hectic the whole process will be.
Congratulations! We have made the backend of our Markdown Blog Application successfully.
Frontend
React
React is a JavaScript library for building user interfaces. The reason behind using ReactJS for the frontend include some points-
- Better Performance
- Reusable components
- Easy to implement
- Runtime
JSX
It is a syntax extension to Javascript. JSX produces React Elements. It has a familiar syntax with plain HTML and it can also be used directly in our JavaScript file but no browser can read it without transpiling it first.
Setting up
Editor using- VSCode
To create the markdown blog app through this tutorial, we are going to install node version-14.
Now open CLI and install React and create a new project using this command.
npx create-react-app react-markdown-blog
The above command will create a directory called react-markdown-blog with React and all dependencies installed in it.
Now navigate to the newly created directory (react-markdown-blog) and run npm start to start the server. This command will automatically launch the browser and redirect it to http://localhost:3000/ (default homepage for react). With no errors in it, we’ll get to see this on our screen.
We are now ready with the structure of the React app, we wanted to create.
Go to react-markdown-blog/src/App.js and let’s first create an example hello world program there to see if things are working or not.
Replace the code of App.Js with the following code.
import React from “react”;
const App = ( ) => {
return (
<div>
<h1>Markdown Blog</h1>
</div>
);
};
export default App;
You can see the Markdown Blog heading on the http://localhost:3000/.
The above code is first importing React from the node modules and then creating a functional component App and then we render the JSX code and then in the end just export the App.
We have some files which we are not going to use in this tutorial, therefore delete all these files from react-markdown-blog/src-
- App.css
- App.test.js
- Index.css
- logo.svg
- setupTests.js
Navigate to the index.js file and delete the third import (index.css).
The next step is to create a folder Components in the src folder.
The first thing in the blog app is to create a new post. Create a file CreateNewPost.jsx and add the following code to the file.
import React from “react”;
const CreateNewPost = () => {
return (
<>
<form>
<h1>Create New Post</h1>
<input type =”text” placeHolder=”title” size=”39" required></input>
<br />
<br />
<textarea placeHolder=”shortDescription” rows=”8" cols=”41"required></textarea>
<br />
<br />
<textarea placeHolder=”contents” rows=”8" cols=”41"required></textarea>
<br />
<br />
<button>Save Post</button>
</form>
</>
);
};
export default CreateNewPost;
As we said earlier, here we are using JSX format and in this code, we have done nothing but just created an HTML form with three inputs- Title, ShortDescription, and Content.
We have to now display these changes and for this, we have to navigate to react-markdown-blog/src/App.js and add this import line in the code.
import CreateNewPost from ‘./components/CreateNewPost’
Now remove the <h1>Markdown Blog</h1> and to render the CreateNewPost component replace it with <CreateNewPost />.
The React app will now look like this.
As of now, we have not added the functionalities in the Save Post button and no CSS is there but we’ll add it in the coming steps.
The next component we are going to add is Post.jsx and add the following code into it.
import React from ‘react’;
const Post = () => {
return (
<>
<section>
<h3>Post title will appear here</h3><p> Short Description will appear here</p>
<p> Post contents will appear here</p>
</section>
</>
)
}
export default Post
A simple code is added to display how a post will look like. Now navigate to the App.js and replace it with the following code to see how the Post component will look like.
import React from “react”;
import Posts from ‘./Components/Post’
const App = ( ) => {
return (
<>
<Posts />
</>
);
};
export default App;
Refresh the browser and it will look like the screenshot below.
The last component we are going to add is DisplayAllPosts.jsx. Initially add the following code to it.
import React from ‘React’;
import CreateNewPost from ‘./CreateNewPost’
const DisplayAllPosts = () => {
return (
<>
<CreateNewPost />
</>
)
}
export default DisplayAllPosts
We have rendered the CreateNewPost component in the DisplayAllPosts.
The next target is to take input from the user and store it directly into the component state variable as they type it. In order to achieve this, we are going to use React usestate.
When we declare a state variable with useState, it returns an array with two items. The first item is the current value, and the second item is its updater function that is used to update the state.
So we are going to implement this in DisplayAllPosts by replacing it with the following code.
import React, {useState} from ‘react’;
import CreateNewPost from ‘./CreateNewPost’
const DisplayAllPosts = () => {
const [title, setTitle] = useState(“”);const [shortDescription, setShortDescription]= useState(“”);
const [content, setContent] = useState(“”);
const savePostTitleToState = event => {
setTitle(event.target.value);
console.log(title)
};const saveShortDescriptionToState = event =>{
setShortDescription(event.target.value);
console.log(shortDescription)
};
const savePostContentToState = event => {
setContent(event.target.value);
console.log(content)
};
return (
<>
<CreateNewPost
savePostTitleToState = {savePostTitleToState}saveShortDescriptionToState={saveShortDescriptionToState}
savePostContentToState = {savePostContentToState}
/>
</>
)
}
export default DisplayAllPosts
Here we have created three variable title, shortDescription and content and set their updater functions setTitle, setShortDescription and setContent respectively. Then we created three functions savePostTitletoState, saveShortDescriptionToState and savePostContentToState.
These functions will be used for saving the user input value into the state. We also added a console.log() statement to each function to view the input value as the user type in their input. Then we pass the three functions down as props into CreateNewPost Component.
Now open CreateNewPost and update it with the following code.
import React from “react”;
const CreateNewPost = props => {
return (
<>
<form>
<h1>Create New Post</h1>
<input type =”text” onChange={props.savePostTitleToState}
placeHolder=”title” size=”39" required></input>
<br />
<br />
<textarea onChange={props.saveShortDescriptionToState}
placeHolder=”shortDescription” rows=”8" cols=”41"required></textarea>
<br />
<br />
<textarea onChange={props.savePostContentToState}
placeHolder=”contents” rows=”8" cols=”41"required></textarea>
<br />
<br />
<button>Save Post</button>
</form>
</>
);
};
export default CreateNewPost;
You can preview the changes in the chrome by using Ctrl+Shift+i command and whenever you type anything in the form it will be visible in the console too.
Next, we want to save our captured post title, shortDescription and content into another state variable called allPosts once a user clicks on the ‘Save Post’ button.
Create a new state variable –
const [allPosts, setAllPosts] = useState([]);
Then create a function savePost in DisplayAllPosts-
const savePost = () => {
const id = Date.now();
setAllPost([…allPost, {title,shortDescription, content, id}]);
console.log(allPost);
};
This function will save the input text into allPost state variables. We have assigned a unique id to each post by putting Date.now() into it.
We’ve also added a console.log() for allPosts. After the data has been captured successfully, we want to clear our state and all the input field values so that the user can add another post. To do that, we will have to clear our title and content state variables.
Let’s update our savePost function like this-
const savePost = () => {
setAllPosts([…allPosts, { title, shortDescription, content }]);
setTitle(“”);
setContent(“”);
console.log(allPosts);
};
In order to locate the input fields on the DOM and clear their value, we’re going to use React Hook useRef. Replace the import statement with- import React, { useState, useRef } from “react”;
Initialize useRef-
const getTitle = useRef();
const getShortDescription = useRef();const getContent = useRef();
Then pass the references to the CreateNewPost component as props-
<CreateNewPost
savePostTitleToState = {savePostTitleToState}
saveShortDescriptionToState={saveShortDescriptionToState}
savePostContentToState = {savePostContentToState}
getTitle={getTitle}
getShortDescription={getShortDescription}
getContent={getContent}
/>
Now we are going to add references in every input from the CreateNewPosts component. The new code will look like this-
import React from “react”;
const CreateNewPost = props => {
return (
<>
<form onSubmit={props.savePost}>
<h1>Create New Post</h1>
<input
type=”text”
onChange={props.savePostTitleToState}
placeholder=”title”
size=”39"
required
ref={props.getTitle}
></input>
<br />
<br />
<textarea
onChange={props.saveShortDescriptionToState}
placeholder=”shortDecription”
rows=”8"
cols=”41"
required
ref={props.getShortDescription}
></textarea>
<br />
<br />
<textarea
onChange={props.savePostContentToState}
placeholder=”contents”
rows=”8"
cols=”41"
required
ref={props.getContent}
></textarea>
<br />
<br />
<button>Save Post</button>
</form>
</>
);
};
export default CreateNewPost;
Now you can open the console and save the post and can see a list of AllPosts there.
We want to see the content of CreateNewPost only when someone clicks on Create New. Therefore replace the code of DisplayAllPosts with the following changes.
import React, { useState, useRef } from “react”;
import CreateNewPost from “./CreateNewPost”;
const DisplayAllPosts = () => {
const [title, setTitle] = useState(“”);
const [shortDescription, setShortDescription]= useState(“”);
const [content, setContent] = useState(“”);
const [allPosts, setAllPosts] = useState([]);
const [isCreateNewPost, setIsCreateNewPost] = useState(false);// Initialize useRef
const getTitle = useRef();
const getContent = useRef();
const getShortDescription = useRef();const savePostTitleToState = event => {
setTitle(event.target.value);
};
const saveShortDescriptionToState = event =>{
setShortDescription(event.target.value);
};
const savePostContentToState = event => {
setContent(event.target.value);
};
const toggleCreateNewPost =()=>{
setIsCreateNewPost(!isCreateNewPost)
}const savePost = event => {
event.preventDefault();
setAllPosts([…allPosts, { title, shortDescription, content }]);
console.log(allPosts);
getTitle.current.value = “”;
getShortDescription.current.value = “”;
getContent.current.value = “”;
};if(isCreateNewPost){
return (
<>
<CreateNewPost
savePostTitleToState={savePostTitleToState}
savePostContentToState={savePostContentToState}
saveShortDescriptionToState={saveShortDescriptionToState}
getTitle={getTitle}
getShortDescription={getShortDescription}
getContent={getContent}
savePost={savePost}
/>
</>
);
}
return (
<>
<h2>All Posts</h2>
<br/>
<br/>
<button onClick={toggleCreateNewPost}>Create New</button>
</>
)
};
export default DisplayAllPosts;
The output will look like this-
We created a new state variable called isCreateNewPost and we initialized it with a boolean value, false.
Then we created another function called toggleCreateNewpost, this function will make isCreateNewPost state variable to toggle between true and false. If the previous state value of isCreateNewPost is true, toggleCreateNewpost will change it to false otherwise, true.
We’ve also added a button Create New into it which will call toggleCreateNewPost when a user clicks on it. A conditional statement is added which renders CreateNewPost only when it is clicked or, say, the boolean is True.
Now we want to see the content on the screen also which is saved by CreateNewPost. For this let us make some changes in Post.jsx first.
import React from ‘react’;
const Post = (props) => {
return (
<>
<section>
<h3>{props.title}</h3>
<p>{props.shortDescription}</p>
<p> {props.content}</p>
</section>
</>
)
}
export default Post
Here we are displaying the post data through the DisplayAllPosts component. The code will show There is nothing to see here if no post is created and will map the list of posts created if we’ve created some posts. Refresh the browser, click on Create New button and create a new post the output will be this-
Now you can make CSS changes to the code or you can also use the styles file made by us from Github directly.
Connecting Frontend to Backend
Now we have to first create a Post request when the Save Post button will get clicked and will save the data into the backend created by Amplication.
Install three modules before proceeding-
- npm install request
- npm install axios
- npm install react-markdown
Change the DisplayAllPosts code from the following code-
import React, { useState, useRef } from “react”;
import CreateNewPost from “./CreateNewPost”;
import Post from “./Post”;
const DisplayAllPosts = () => {
const [title, setTitle] = useState(“”);
const [shortDescription, setShortDescription]= useState(“”);
const [content, setContent] = useState(“”);
const [allPosts, setAllPosts] = useState([]);
populateAllPosts(setAllPosts);const [isCreateNewPost, setIsCreateNewPost] = useState(false);
// Initialize useRef
const getTitle = useRef();
const getContent = useRef();
const getShortDescription = useRef();const savePostTitleToState = event => {
setTitle(event.target.value);
};
const saveShortDescriptionToState = event =>{
setShortDescription(event.target.value);
};
const savePostContentToState = event => {
setContent(event.target.value);
};
const toggleCreateNewPost =()=>{
setIsCreateNewPost(!isCreateNewPost)
}const savePost = event => {
event.preventDefault();
const id = Date.now();
setAllPosts([…allPosts, { title,shortDescription, content, id }]);
console.log(allPosts);
var request = require(‘request’);
var options = {
‘method’: ‘POST’,
‘url’: ‘http://localhost:3000/api/articles/',
‘headers’: {
Authorization: ‘Basic YWRtaW46YWRtaW4=’,
‘Content-Type’: ‘application/json’
},body:JSON.stringify({
“content”: content,
“shortDescription”:shortDescription,
“title”:title
})
};
request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body);
});
toggleCreateNewPost();
};if(isCreateNewPost){
return (
<>
<CreateNewPost
savePostTitleToState={savePostTitleToState}
savePostContentToState={savePostContentToState}
saveShortDescriptionToState={saveShortDescriptionToState}
getTitle={getTitle}
getShortDescription={getShortDescription}
getContent={getContent}
savePost={savePost}
/>
</>
);
}return (
<>
<h2>All Posts</h2>
{!allPosts.length ? (
<div>
<h3>There is nothing to see here!</h3>
</div>
) : (
allPosts.map(eachPost => {
return (
<Post
id={eachPost.id}
key={eachPost.id}
title={eachPost.title}
shortDescription={eachPost.shortDescription}
content={eachPost.content}
/>
);
})
)}
<br />
<br />
<button onClick={toggleCreateNewPost}>Create New</button>
</>
);
};var populateAllPosts = async function(setAllPosts) {
var axios = require(‘axios’);
var config = {
method: ‘get’,
url: ‘http://localhost:3000/api/articles/’,
headers: {
Authorization: ‘Basic YWRtaW46YWRtaW4=’,
‘Content-Type’: ‘application/json’
}
};
await axios(config).then(
function(response) {
console.log(response.data)
setAllPosts(response.data)
}
)
};
export default DisplayAllPosts;
In the above code, we’ve simply done a POST and a GET request. For the GET request, we are using the request module and for the POST request, we are using the axios module.
Here to make things simpler we are sending a header to surpass the BasicAuth and to give access to every request without authorization.
We are using the Article API created by Amplication in the previous steps. Now we have connected the frontend to the backend and you can access the previous articles created and also can create new ones which will be stored in the backend database.
Adding Markdown-
Replace the code of Post.jsx from the following code-
import React from “react”;
import ReactMarkdown from “react-markdown”const Post = ({ title, content, shortDescription, id, deletePost }) => {
return (
<>
<section className=”post-container”>
<h2>{title}</h2>
<br />
<h3><b>
Short Description</b>
</h3>
<p> {shortDescription}</p>
<h3><b>
Content </b>
</h3>
<ReactMarkdown>
{content}
</ReactMarkdown>
</section>
</>
);
};
export default Post;
Here we’ve used the React-Markdown module to render the Content of articles in Markdown format.
Now it’s up to you to add some CSS or if you like, you can copy the styles.css file and add it to your code from the Github repository.
The final output will look like this-
You can have a look at the full code in the following Github Repositories-
Github –
- Frontend
https://github.com/souravjain540/React-markdown-blog-frontend
2. Backend
https://github.com/souravjain540/markdown-blog
Some important links related to the article-
- Amplication Docs
- React Docs
- Amplication Discord (For the community help if you got some error, I used this too)