Build a full-stack application with Amplication
Amplication is a versatile open-source Node.js app development platform that allows you to quickly create a production-ready Node.js backend without wasting time on repetitive coding. Furthermore, a pre-built UI or CLI simplifies the creation of data models and role-based access configuration. You can use Amplication to push your generated Application to GitHub, providing you with a containerized Node.js Typescript database application and a React client. NestJS, Prisma, REST, GraphQL API, React Admin UI, logging, user authentication, and authorization are all configured in all applications. It also enables developers to host applications on their servers using a Docker container or the Amplication cloud.
To get started building applications with Amplication, ensure you have the following installed:
- Node.js version 14 or later
- PostgreSQL database
- Prisma CLI (
npm install -g @prisma/cli
) - Nestjs CLI (
npm install @nest/cli
)
Create the backend
Once the above requirements are met, let’s start by creating the backend part of our blog application. To begin, create an Amplication account to sign in to your dashboard if this is the first time you’ve done that.
Click the + Add New project button on your Amplication home page to create a new project.
Then enter the project name. For example, we’ll name the Application BlogApp for this demonstration and press the Create Project button.
Create a Blog Entity
To create an entity in an Amplication project, first, you need to create a service. To do that, click the Add Resources button and select Service.
Now on the create resource page, check the features you’d want in your project. We’ll leave all the features contained for this demonstration and then click the Create Service button.
In the above selection, we are adding the feature for GraphQL API, REST API & Swagger UI, and Admin UI to our project. We also specified that we want to create entities by checking None on the Sample Entities option.
Now you’ll be redirected to the Service dashboard.
Next, click the Go to entities button in the Entities tab. The Application already has a User entity for you by default. So click on the Add Entity button to create a Blog entity to store our blog data. Enter the Entity name and press the Create Entity button.
Right here, you are provided with some input boxes to allow you to define the fields of your Entity. For example, we’ll create name, description, and content fields for our’ Blog’ entity.
Add Permissions to Entity
Now let’s add permissions to our Blog entity. By default, the Amplication entity has CRUD permission, but you can decide to grant permissions to a specific user or user role. For this demonstration, we’ll give a public access to the Blog Entity so we can test things out to know how it works.
So, click on the Permissions and check the public box for all the services.
Build the Application
Now enter your commit message and commit the changes by clicking the Commit changes and build button.
Once the commit is completed, click on the last commit number to download the source code for the project.
Once the download is completed, unzip the downloaded file. You’ll have a server and admin-ui folders. Open the server folder in your favorite IDE.
Create Custom Endpoints
By default, Amplication creates all the CRUD endpoints for all the entities in the Application. But the authentication module creates the login endpoint, so we need to modify the server to make a custom endpoint for the signup route. To do that, update the AuthService
in the server/src/auth/auth.service.ts
with the code snippet below.
//...
async signup(credentials: Credentials): Promise<UserInfo> {
// Extract the username and password from the body of the request
const { username, password } = credentials;
// Here, we attempt to create a new user
const user = await this.user service.create({
data: {
username,
password,
roles: [],
},
});
// If creating a new user fails, throw an error
if (!user) {
throw new UnauthorizedException("Could not create user");
}
// Create an access token for the newly created user
const accessToken = await this.tokenService.createToken({
id: user.id,
username,
password,
});
// Return the access token as well as some details about the user
return {
accessToken,
username: user.username,
id: user.id,
roles: []
};
}
//...
In the above code snippet, we added a signup method to allow users to create an account. This function will take the user’s credentials as a parameter and return a promise of type UserInfo
. Once the user is created, we’ll generate an access token for the user to allow them to log in to the Application.
Then update the AuthController
in the server/src/auth/auth.controller.ts
file with the code snippet below.
//...
@Post("signup")
async signup(@Body() body: Credentials): Promise<UserInfo> {
return this.authService.signup(body);
}
//...
In the above code, we added a signup route handler to use the signup method we created in the AuthService
and accept the user input from the request payload.
Next, update the AuthMutation
class in the server/src/auth/auth.resolver.ts
file to crate a GraphQL mutation for the AuthService signup method.
//...
@Mutation(() => UserInfo)
async signup(@Args() args: LoginArgs): Promise<UserInfo> {
return this.authService.signup(args.credentials);
}
//...
Run the Application
Now let’s test out our Application’s server and Admin UI. To run the server, run the command below to run the Docker container for the default PostgreSQL connection created for your Application.
npm run docker:DB
Then run the commands below to generate the Prisma client and create the schema for the database.
npm run Prisma:generate
npm run DB:init
Now start the Application with the command below.
npm run start
Then to start the Admin UI, move into the admin-UI folder and start the admin with the command below.
cd ../admin-UI
npm run start
You should see the login page, where you can choose which way to access or manipulate your Application’s data.
So go ahead and use GraphQL, Admin UI, or Swagger to add blog data to your Application.
Session Replay for Developers
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.
Create the frontend
At this point, our backend has been fully configured. So, let’s set up the frontend part of our Application.
To get started, create a new react application with the command below.
npx create-react-app app
Once the installation is completed, install axios
to allow us to make API requests to the backend with the command below.
npm i axios
Then start the Application by running the command below.
npm run start
Create the UI components
Now let’s create the components UI for our Application. First, generate the components folder in the src directory. Then in the components folder, create an Auth.js
file for the authentication UI with code snippets below.
export const Auth = ({ setActiveUser }) => {
return (
<div className="auth-container">
<div className="auth-wrapper">
<div className="login">
<form>
<h4>Login Here</h4>
<input type="text" placeholder="username"/>
<input type="text" placeholder="password"/>
<button>Login</button>
</form>
</div>
<div className="signup">
<form>
<h4>Signup Here</h4>
<input
type="text"
placeholder="first name."
/>
<input
type="text"
placeholder="last name."
/>
<input
type="text"
placeholder="username"
/>
<input
type="text"
placeholder="password"
/>
<button>Signup</button>
</form>
</div>
</div>
</div>
);
};
Then update the App.js
file to render the Auth component with the code snippet below.
//...
import { Auth } from "./components/Auth";
function App() {
return (
<div className="App">
<Auth/>
</div>
);
}
export default App;
Now copy this Application’s styles from the GitHub repository and update the styles in the App.css
file. So you open the Application on your browser, you’ll see the output below:
Next, I created a Header.js
file in the components folder to render the navbar of the Application with the code snippet below.
export const Header = ({ setActiveUser }) => {
return (
<div className="nav">
<ul>
<li>
<p>Home</p>
</li>
<li>
<p>Contact Us</p>
</li>
<li>
<p className="logout">
logout
</p>
</li>
</ul>
</div>
);
};
Then, create a Blog.js
file in the components folder to render the blogs from our backend API.
export const Blog = () => {
return (
<div>
<div className="wrapper">
<p className="header">Blog Name</p>
<p className="content">Blog content</p>
<p className="leesmeer">Read More</p>
</div>
))}
</div>
);
};
Now render the Header and Blog components in the App.js
file with code snippets below.
///...
import { Blog } from "./components/Blog";
import { Header } from "./components/Header";
import { useState } from "react";
function App() {
const [activeUser, setActiveUser] = useState(false);
return (
<div className="App">
{activeUser ? (
<>
<Header setActiveUser={setActiveUser}/>
<Blog />
</>
) : (
<Auth setActiveUser={setActiveUser} />
)}
</div>
);
}
export default App;
In the above code snippet, we created an activeUser
state variable to check if a user is logged in to render the Header and Blog components, else the Auth component.
Consume the backend APIs
Now let’s consume the backend APIs to signupand login a user and to fetch the Blog from and render to the **Blog **component. First, let’s start with the Auth component and update the component/Auth.js
file with the code snippets below.
import axios from "axios";
import { useState } from "react";
export const Auth = ({ setActiveUser }) => {
const [firstname, setFirstname] = useState();
const [lastname, setLastname] = useState();
const [username, setUsername] = useState();
const [password, setPassword] = useState();
const signup = async (e) => {
e.preventDefault();
const res = await axios.post("http://localhost:3000/api/signup", {
firstname,
lastname,
username,
password,
});
if (res.data) {
localStorage.setItem("accessToken", res.data.accessToken);
setActiveUser(res.data);
}
};
const login = async (e) => {
e.preventDefault();
const res = await axios.post("http://localhost:3000/api/login", {
username,
password,
});
if (res.data) {
localStorage.setItem("accessToken", res.data.accessToken);
setActiveUser(res.data);
}
};
return (
<div className="auth-container">
<div className="auth-wrapper">
<div className="login">
<form onSubmit={login}>
<h4>Login Here</h4>
<input type="text" placeholder="username" onChange={(e) => setUsername(e.target.value)}/>
<input type="text" placeholder="password" onChange={(e) => setPassword(e.target.value)}/>
<button>Login</button>
</form>
</div>
<div className="signup">
<form onSubmit={signup}>
<h4>Signup Here</h4>
<input
type="text"
placeholder="firstname"
onChange={(e) => setFirstname(e.target.value)}
/>
<input
type="text"
placeholder="lastname"
onChange={(e) => setLastname(e.target.value)}
/>
<input
type="text"
placeholder="username"
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="text"
placeholder="password"
onChange={(e) => setPassword(e.target.value)}
/>
<button>Signup</button>
</form>
</div>
</div>
</div>
);
};
In the above code snippet, we imported the axios package we installed earlier and created a state variable for the inputs we send to the backend. Then we started two functions to handle the signup and login operations. In both functions, we used Axios to make an API request to the http://localhost:3000/api/login
and http://localhost:3000/api/signup
endpoints. Once the request is successful, we update the active user state and save the user’s token in the local storage.
Next, update the Blog.js
file to get the blogs from the backend with the code snippets below.
import { useEffect, useState } from "react";
import axios from "axios";
export const Blog = () => {
const accessToken = localStorage.getItem("accessToken");
const [blogs, setBlogs] = useState([]);
useEffect(() => {
const getBlogs = async () => {
const res = await axios.get("http://localhost:3000/api/blogs", {
headers: {
'Authorization': `Bearer ${accessToken}`,
},
});
if (res.data) {
setBlogs(res.data);
}
};
getBlogs();
}[accessToken]);
return (
<div>
{blogs && blogs.map((blog) => (
<div className="wrapper" key={blog.id}>
<div className="pic"></div>
<p className="header">{blog.name}</p>
<p className="content">{blog.content}</p>
<p className="leesmeer">Read More</p>
</div>
))}
</div>
);
};
Here we sent a request to the http://localhost:3000/api/blogs
endpoint using Axios, and when data is fetched from the request, we store them in the blogs state variable. In our request headers, we add the following. Then we loop through the Blog and render them to the users.
Lastly, update the code in the Header component to implement a logout functionality with the code snippet below.
export const Header = ({ setActiveUser }) => {
const logout = () => {
localStorage.clear();
setActiveUser("");
};
return (
<div className="nav">
<ul>
<li>
<p>Home</p>
</li>
<li>
<p>Contact Us</p>
</li>
<li>
<p className="logout" onClick={logout}>
logout
</p>
</li>
</ul>
</div>
);
};
So if you refresh the Application and sign up or log in, you’ll see the Blog components with blogs you have created from the admin UI, as shown below.
Conclusion
Amplication is an excellent tool for building a web application with minimal backend code. Feel free to check out other features of Amplication documentation.
In this article, we’ve explored how to build a full-stack blog application using Amplication. We started with a detailed explanation of what it is. Then went to creating a React frontend for the UI and consuming the backend APIs. I hope you enjoyed this article! Happy coding!
A TIP FROM THE EDITOR: On the topic of using containers to deploy your site, don’t miss our Dockerizing Full-Stack React Apps article.