Back

Build a full-stack application with Amplication

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.

1

Then enter the project name. For example, we’ll name the Application BlogApp for this demonstration and press the Create Project button.

2

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.

3

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.

4

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.

5

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.

6

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.

7

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.

8

Build the Application

Now enter your commit message and commit the changes by clicking the Commit changes and build button.

9

Once the commit is completed, click on the last commit number to download the source code for the project.

10

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.

11

So go ahead and use GraphQL, Admin UI, or Swagger to add blog data to your Application.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data. OpenReplay Start enjoying your debugging experience - start using OpenReplay for free.

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:

12

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.

13

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.

newsletter