Thursday, September 15, 2022
HomeWeb DevelopmentMethods to construct a full-stack app in RedwoodJS

Methods to construct a full-stack app in RedwoodJS


On this information, we are going to stroll by way of the way to construct a full-stack utility with RedwoodJS.

You could have seen a number of tutorials and guides round constructing a full-stack utility with x framework (or) expertise, however, RedwoodJS is completely different and useful in sure methods, together with:

  1. RedwoodJS contains Typescript, GraphQL, Prisma, and a testing framework
  2. Startups can construct and prototype a product because it gives modules equivalent to authentication, authorization, and CRUD operations. All we have to do is to design enterprise logic for our necessities
  3. CLI is likely one of the greatest options of RedwoodJS; it makes the event course of quicker and simpler

Check out this demo to what the ultimate app will seem like from this tutorial:

Redwood_LogRocket.mp4

Dropbox is a free service that allows you to deliver your images, docs, and movies anyplace and share them simply. By no means e-mail your self a file once more!

Right here we’re going to construct a discussion board to know how RedwoodJS apps are constructed. It contains all of the functionalities that assist you perceive all of the frameworks’ features.

The functionalities that we’re going to construct are:

  • Login and signup
  • Create, learn, and replace posts
  • Commenting system
  • Consumer-based entry on posts

Together with RedwoodJS, we are going to use Typescript for kind checking and TailwindCSS for styling.

Desk of Contents

RedwoodJS set up and setup

RedwoodJS makes use of yarn as a package deal supervisor. As soon as you put in it, you may create a brand new venture utilizing the next command:

yarn create redwood-app --ts ./redwoodblog

It scaffolds all of the modules to construct a full-stack utility. Right here, you may see the entire construction of a RedwoodJS utility.

Redwood JS app folder structure

There are three main directories. They’re api, scripts, and net. Let’s talk about them intimately.

  • .redwood: Accommodates the construct of the applying.
  • api: Serves the backend of the applying. It primarily comprises db, which serves the database schema of the applying. All backend functionalities can be in src listing
    • src: Accommodates all of your backend code. It comprises 5 directories, that are as follows:
      • directives: Accommodates GraphQL schema directives to regulate entry to GraphQL queries
      • features: RedwoodJS runs the GraphQL API as serverless features. It auto-generates graphql.ts; you may add further serverless perform on high of it
      • graphql: Accommodates GraphQL schema written in schema definition language (SDL)
      • lib: Accommodates all of the reusable features throughout the backend API. for instance, authentication
      • providers: Accommodates enterprise logic associated to your information. It runs the performance associated to the API and returns the outcomes

Organising TailwindCSS

Putting in TailwindCSS is easy; run the next command within the root listing:

yarn rw setup ui tailwindcss

Installing packages

To substantiate the set up of TailwindCSS, go to net/src/index.css and see the Tailwind lessons in that file.

Connecting database

To attach the Postgres database, we are going to use Docker for native growth.

(Word: To put in docker, see the documentation from official docker web site)

Create docker-utils/postgres-database.sh in root listing and add the next script:


Extra nice articles from LogRocket:


#!/bin/bash

set -e
set -u

perform create_user_and_database() {
        native database=$1
        echo "  Creating consumer and database '$database'"
        psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
            CREATE USER $database;
            CREATE DATABASE $database;
            GRANT ALL PRIVILEGES ON DATABASE $database TO $database;
EOSQL
}

if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then
        echo "A number of database creation requested: $POSTGRES_MULTIPLE_DATABASES"
        for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
                create_user_and_database $db
        finished
        echo "A number of databases created"
fi

This script implements a perform to create a consumer and database in Postgres. When you create the script, you should use docker-compose up to run the Postgres database.

Create docker-compose.yml and add the next code:

model: "3.6"
providers:
  postgres:
    picture: postgres
    restart: unless-stopped

If you wish to create completely different variations of docker-compose up based mostly on the atmosphere, you are able to do that as nicely. To do that, create docker-compose.override.yml and add the next code:

model: "3"
providers:
  postgres:
    picture: postgres
    atmosphere:
      - POSTGRES_USER=api
      - POSTGRES_PASSWORD=development_pass
      - POSTGRES_MULTIPLE_DATABASES="redwoodforum-api","redwoodforum-api-testing"
    volumes:
      - ./docker-utils:/docker-entrypoint-initdb.d
      - redwoodforum_api_data:/information/postgres
    ports:
      - 5440:5432
volumes:
  redwoodforum_api_data: {}

When you add the script, you may run the database utilizing this command:

docker-compose up

Running database

To attach a Redwood utility to Postgres, change the Prisma configuration to a PostgreSQL supplier and add database URL in an atmosphere variable.

Go to api/db/schema.prisma and alter the db supplier to postgresql. Add DATABASE_URL in your .env.

DATABASE_URL=postgres://api:[email protected]:5440/redwoodforum-api

Designing database

As you may see within the demo, we wish to construct a discussion board. Nevertheless, earlier than we implement the performance, Listed below are the important thing issues we wish customers to have the ability to do in our utility:

  • Customers can log in/signup into the app
  • As soon as customers log in, they’ll create a publish within the discussion board
  • Customers can touch upon any publish, and the proprietor can delete any feedback
  • Consumer can view their publish and go to the House web page to view all posts

Let’s design an ER diagram for the applying.

ER diagram for application depicts user comment and post

Right here we’ve the consumer, publish, and remark schemas.

consumer and publish have a one-to-many relationship, and publish and remark have a one-to-many relationship, whereas remark and consumer have a one-to-one relationship.

Now we’ve the ER diagram for the applying. let’s create the schema for the database. For that, go to api/db/schema.prisma.

(Word: RedwoodJS makes use of Prisma for database. In case you’re new to Prisma world, try their documentation for extra info)

Now, create the schemas in a Prisma file:

mannequin Consumer {
  id           Int            @id @default(autoincrement())
  e-mail        String         @distinctive
  identify         String?
  hashedPassword     String
  salt String
  resetToken String?
  resetTokenExpiresAt DateTime?
  posts   Put up[]
  feedback Remark[]
}

mannequin Put up {
  id           Int            @id @default(autoincrement())
  title        String
  physique         String
  feedback     Remark[]
  writer     Consumer    @relation(fields: [authorId], references: [id])
  authorId   Int
  createdAt    DateTime  @default(now())
  updatedAt    DateTime  @default(now())
}

mannequin Remark {
  id     Int    @id @default(autoincrement())
  physique   String
  publish   Put up   @relation(fields: [postId], references: [id])
  postId Int
  writer Consumer   @relation(fields: [authorId], references: [id])
  authorId Int
  createdAt DateTime @default(now())
  updatedAt DateTime @default(now())
}

As you may see, we’ve a relationship between the Consumer, Put up, and Remark schemas.

Defining a relationship in Prisma is easy. You may consult with the documentation to study extra intimately.

When you outline a schema in Prisma, you have to run the migration to create these schemas as a desk in Postgres.

Prisma migration

One of many options of Prisma is you can handle migration for various phases. Right here, we’re going to run the migration just for growth. For that, you should use this command:

yarn redwood prisma migrate dev

Database now in sync with schema

To examine if the migration was profitable, you may go to Prisma Studio and see all of the tables after migration. you may see all of the tables and columns inside of every desk by visiting http://localhost:5555.

yarn redwood prisma studio

Now, we’ve database and schema for the API and frontend, let’s create authentication for the applying.

Authentication

RedwoodJS gives authentication out of the field. A single CLI command will get you every thing that you must get authentication working.

yarn rw setup auth dbAuth

It’s going to create a auth.ts serverless perform that checks the cookie if the consumer exists within the database and token expiry. Then, it returns the response based mostly on that to a consumer.

It additionally creates lib/auth.ts to deal with functionalities, equivalent to getCurrent consumer from session, examine if authenticated, require authentication and many others.

To this point, we’ve the authentication performance for the API and database. Let’s create pages for login, signup, and forgot password. Then, you should use the command to scaffold the login, signup, and forgot password pages.

yarn rw g dbAuth

It’s going to create all of the pages for authentication. You may examine these pages at net/src/pages.

Looking at files under pages folder in overall structure

For styling the pages, you should use the elements from the supply code and customise them based mostly in your preferences. Right here is the entire login web page from the implementation:

Login page

To attach an API for login and signup performance, RedwoodJS gives hooks that do all of the magic below the hood.

import { useAuth } from '@redwoodjs/auth'

// gives login and signup performance out of the field
const { isAuthenticated, signUp, logIn, logOut } = useAuth()

Within the type onSubmit perform, we are able to use that signup and logIn to make the API request and ship the payload.

const onSubmit = async (information) => {
    const response = await signUp({ ...information })

    if (response.message) {
      toast(response.message)
    } else if (response.error) {
      toast.error(response.error)
    } else {
      // consumer is signed in robotically
      toast.success('Welcome!')
    }
  }

As soon as the consumer indicators up or logs in, you may entry the consumer info throughout the applying utilizing currentUser.

const { currentUser } = useAuth()

Now, we’ve the consumer logged in to the applying. Subsequent, let’s construct the performance to publish and remark.

As soon as the customers log in, they land on the house web page, the place we have to present all of the posts within the discussion board. Then, the consumer can create a brand new publish and replace a publish.

To implement the itemizing web page, create a route with the House web page element and fetch the information from the API to indicate it on the consumer aspect.

Fortunately, RedwoodJS gives scaffolding that generates all of the implementation for us. Let’s say you wish to scaffold all of the pages, together with GraphQL backend implementation, you should use the next command:

yarn redwood g scaffold publish

It’s going to generate pages, SDL, and repair for the publish mannequin. You may consult with all of the RedwoodJS instructions of their documentation.

Since we’re going to customise the pages. Let’s scaffold SDL and providers solely. Use this command:

yarn redwood g sdl --typescript publish

It’s going to create publish area recordsdata in graphql/posts.sdl.ts and providers/posts — let’s create pages on the internet.

Regardless that we customise the pages and elements, we don’t must create every thing from scratch. As a substitute, we are able to use scaffolding and modify it based mostly on our necessities.

Let’s create a House web page utilizing this command:

yarn redwood g web page dwelling

It’s going to create a house web page and add that web page contained in the Routes.tsx. So now, you could have the essential dwelling web page element.

Now, to listing all of the posts on the house web page, that you must fetch the information from api and present it on the pages. To make this course of simpler, RedwoodJS gives cells. Cells are a declarative method to information fetching — it executes a GraphQL question and manages its lifecycle.

To generate cells, use this command:

yarn rw generate cell dwelling

It’s going to create a GraphQL question and its lifecycle:

import kind { FindPosts } from 'varieties/graphql'

import { Hyperlink } from '@redwoodjs/router'
import kind {
  CellSuccessProps,
  CellFailureProps,
  CellLoadingProps,
} from '@redwoodjs/net'

export const QUERY = gql`
  question FindPosts {
    posts {
      id
      title
      physique
      feedback {
        id
      }
      createdAt
      updatedAt
    }
  }
`

export const Loading: React.FC<CellLoadingProps> = () => <div>Loading...</div>

export const Empty = () => <div>No posts discovered</div>

export const Failure = ({ error }: CellFailureProps) => (
  <div>Error loading posts: {error.message}</div>
)

export const Success = ({ posts }: CellSuccessProps<FindPosts>) => {
  return (
    <div>
      <ul>
        {posts.map((publish) => (
          <li key={publish.id}>
            {' '}
            <Hyperlink to={`/posts/${publish.id}`}>
              <div className="p-2 my-2 rounded-lg shadow cursor-pointer">
                <h4 className="text-xl font-medium">{publish.title}</h4>

                <p>{publish.physique}</p>
              </div>
            </Hyperlink>
          </li>
        ))}
      </ul>
    </div>
  )
}

Protected route

To guard the route within the RedwoodJS utility, you should use Personal from @redwoodjs/router and wrap every thing contained in the route.

<Personal unauthenticated="login">
        <Set wrap={NavbarLayout}>
          <Set wrap={ContainerLayout}>
            <Route path="/new" web page={NewpostPage} identify="newpost" />
            <Set wrap={SidebarLayout}>
              <Route path="https://weblog.logrocket.com/" web page={HomePage} identify="dwelling" />
              // routes come right here
            </Set>
          </Set>
        </Set>
</Personal>

Creating posts

To create a brand new publish, scaffold a brand new publish web page utilizing the next command:

yarn redwood g web page newpost /new

If you wish to customise the route URL, you may go that as a parameter right here. RedwoodJS provides routes based mostly on the offered identify. RedwoodJS gives varieties and validations out of the field,

import {
  FieldError,
  Kind,
  Label,
  TextField,
  TextAreaField,
  Submit,
  SubmitHandler,
} from '@redwoodjs/varieties'

As soon as a consumer submits type, you may name the GraphQL mutation to create a publish.

const CREATE_POST = gql`
  mutation CreatePostMutation($enter: CreatePostInput!) {
    createPost(enter: $enter) {
      id
    }
  }
`
const onSubmit: SubmitHandler<FormValues> = async (information) => {
    attempt {
      await create({
        variables: {
          enter: { ...information, authorId: currentUser.id },
        },
      })
      toast('Put up created!')
      navigate(routes.dwelling())
    } catch (e) {
      toast.error(e.message)
    }
  }

Put up particulars

Create a publish particulars web page and cell for information fetching to view publish particulars. You may comply with the identical course of that we did earlier than.

yarn redwood g web page postdetails

This can create the web page and route in routes.tsx. To go URL params within the route, you may modify it like this:

<Route path="/posts/{id:Int}" web page={PostDetails} identify="postdetails" />

You may go ID into the element as props. Then, create a cell to fetch the publish particulars and render it contained in the element.

yarn redwood g cell publish

Add the next code to fetch the information and feedback for a particular publish:

import kind { FindPosts } from 'varieties/graphql'
import { format } from 'date-fns'
import { useAuth } from '@redwoodjs/auth'
import kind {
  CellSuccessProps,
  CellFailureProps,
  CellLoadingProps,
} from '@redwoodjs/net'

export const QUERY = gql`
  question FindPostDetail($id: Int!) {
    publish: publish(id: $id) {
      id
      title
      physique
      writer {
        id
      }
      feedback {
        id
        physique
        writer {
          id
          identify
        }
        createdAt
      }
      createdAt
      updatedAt
    }
  }
`

export const Loading: React.FC<CellLoadingProps> = () => <div>Loading...</div>

export const Empty = () => <div>No posts discovered</div>

export const Failure = ({ error }: CellFailureProps) => (
  <div>Error loading posts: {error.message}</div>
)

export const Success = ({ publish }: CellSuccessProps<FindPosts>) => {
  const { currentUser } = useAuth()

  return (
    <div>
      <div>
        <h2 className="text-2xl font-semibold">{publish.title}</h2>
        <p className="mt-2">{publish.physique}</p>
      </div>

      <div className="mt-4 ">
        <hr />
        <h3 className="my-4 text-lg font-semibold text-gray-900">Feedback</h3>
        {publish.feedback.map((remark) => (
          <div
            key={remark.id}
            className="flex justify-between sm:px-2 sm:py-2 border rounded-lg"
          >
            <div className="my-4 flex-1  leading-relaxed">
              <robust>{remark.writer.identify}</robust>{' '}
              <span className="text-xs text-gray-400">
                {format(new Date(remark.createdAt), 'MMM d, yyyy h:mm a')}
              </span>
              <p>{remark.physique}</p>
            </div>
            {currentUser && currentUser.id === publish.writer.id && (
              <div className="m-auto">
                <button
                  kind="button"
                  className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2 darkish:bg-red-600 darkish:hover:bg-red-700 darkish:focus:ring-red-900"
                >
                  Delete
                </button>
              </div>
            )}
          </div>
        ))}
      </div>
    </div>
  )
}

An vital factor to notice right here is the situation to examine if the present logged in consumer is the writer of publish. In that case, we offer an choice to delete feedback.

Consumer-based entry

To offer user-based entry inside the applying, you may get the present consumer utilizing useAuth hooks and add situations on it. For instance, To point out a listing of posts created by the consumer, you should use the present consumer ID to fetch posts by writer.

const { currentUser } = useAuth()

MyPostCell.tsx

import { Hyperlink } from '@redwoodjs/router'
import kind { FindPosts } from 'varieties/graphql'
import kind {
  CellSuccessProps,
  CellFailureProps,
  CellLoadingProps,
} from '@redwoodjs/net'

export const QUERY = gql`
  question FindMyPosts($id: Int!) {
    consumer: consumer(id: $id) {
      id
      identify
      posts {
        id
        title
        physique
      }
    }
  }
`

export const Loading: React.FC<CellLoadingProps> = () => <div>Loading...</div>

export const Empty = () => <div>No posts discovered</div>

export const Failure = ({ error }: CellFailureProps) => (
  <div>Error loading posts: {error.message}</div>
)

export const Success = ({ consumer }: CellSuccessProps<FindPosts>) => {
  return (
    <div>
      <ul>
        {consumer.posts.map((publish) => (
          <li key={publish.id}>
            {' '}
            <Hyperlink to={`/posts/${publish.id}`}>
              <div className="shadow rounded-lg p-2 my-2 cursor-pointer">
                <h4 className="text-xl font-medium">{publish.title}</h4>

                <p>{publish.physique}</p>
              </div>
            </Hyperlink>
          </li>
        ))}
      </ul>
    </div>
  )
}

Conclusion

RedwoodJS gives every thing out of the field. It’s all about constructing the applying based mostly on our necessities. Some vital ideas are cells, pages, Prisma schema and migration, and understanding how the system works.

When you perceive RedWoodJS, you may construct a full-stack utility with little or no time, as we’ve seen on this publish. Yow will discover the supply code for this tutorial right here.

Writing a number of TypeScript? Watch the recording of our latest TypeScript meetup to study writing extra readable code.

TypeScript brings kind security to JavaScript. There could be a rigidity between kind security and readable code. Watch the recording for a deep dive on some new options of TypeScript 4.4.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments