5

RedwoodJS: Bringing Full Stack to the JAMstack – Part 2

 1 year ago
source link: https://devm.io/javascript/javascript-redwoodjs-jamstack
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

The Full-Stack-Framework in Action

RedwoodJS: Bringing Full Stack to the JAMstack – Part 2


In the first part of this series, we looked at the basics of Redwood, a combination of established technologies for implementing web applications. In the second part, we'll work with user input, look at authentication options, testing, and more.

Beginning with a small reminder: on the frontend, RedwoodJS relies on React, GraphQL is used for communication between the frontend and the backend, and on the backend, Redwood uses Node.js and persists the application's data in an SQL database. The special feature of Redwood compared to other approaches is that it makes relatively strict specifications and delivers both client and server from a single source. Another advantage is that with Redwood, deployment for specific platforms, such as Netlify, Vercel, or AWS Serverless, is already prepared and requires virtually no further effort after initial configuration.

So far, you've learned a bit about Redwood's architecture and seen a small example of how data flows from the server to the client. We'll extend this example to include write calls to the server; you'll also learn how to navigate within a Redwood application, how to handle input from your users, and how to use authentication to protect the application from unauthorized access. The whole thing is rounded off with a look at testing.

Writing server communication

The application that will accompany us, as in the first part, is a simple address book. So far we can only display data and the address list in our application was only able to display the address entries. In the next step, we will extend the list with the possibility to delete records as well.

Deleting the records in the backend

In the first part of this article series, we used the yarn rw g scaffold address command to make Redwood create the backend and frontend structures for address management. We then built our own frontend on this basis. The scaffold command creates a number of structures in the backend that ensure that all CRUD operations are possible on the address records. The starting point is the schema definition in the addresses.sdl.js file in the graphql directory. As an alternative to the scaffold command, you can also have only the GraphQL interface generated. To do this, execute the yarn rw d sdl addresses command on the command line. This command creates the schema definition and the associated service. By default, only the query type is created by this command. With the --crud option, you instruct Redwood to generate mutation types as well. For deleting records a mutation type is needed here. GraphQL distinguishes between the reading query types and the modifying mutation types. Listing 1 shows the snippet from the addresses.sdl.js file that contains the GraphQL schema definition.

Listing 1: GraphQL-Schema definition

export const schema = gql`
  type Address {
    id: Int!
    firstname: String!
    lastname: String!
    street: String!
    city: String!
  }
  ...
  type Mutation {
    ...
    deleteAddress(id: Int!): Address!
  }
`

For handling address records, we first define the Address type. Within the mutation type, you then group all modifying operations like creating, updating, and in our case deleting records. The deleteAddress mutation requires the ID of the record to be deleted in the form of an integer and returns the deleted record. Behind every GraphQL interface is an implementation that ensures that the requested data is provided or the desired operation is executed. This role is fulfilled by the services in Redwood.

If you have defined the SDL via the command line, Redwood has automatically created such a service in the services directory. All files of a service, i.e. the service file addresses.js itself and the associated unit tests in the file addresses.test.js can be found in a subdirectory named addresses. In the simplest case, the service passes the request directly to the database, as can be seen in Listing 2. For database access, Redwood uses the Prisma library as ORM, so the database can be accessed via an object notation and SQL statements do not have to be written manually.

Listing 2: Deleting records with Prism

import { db } from 'src/lib/db'
...
export const deleteAddress = ({ id }) => {
  return db.address.delete({
    where: { id },
  })

The combination of GraphQL, Prisma, and Redwood ensures that the actual asynchronous operation is handled properly from the interface to the database and back, so you don't have to worry about asynchronous flow control yourself. With these backend structures, a GraphQL client can access the interface, trigger the deleteAddress mutation, and delete records. So that the users of your application can also use the new feature, you must integrate a possibility for the deletion of the data records into the frontend. Here the list of records is a good choice and there a button per record that triggers the delete operation.

Deleting the records in the frontend

The presentation of the address records in the frontend is divided into several parts: The ListPage forms the frame and is bound to the default route / so that it is displayed when users open the application in the browser. The AddressesLayout defines the frame of the view with a header and footer. The Page component integrates the Layout component and includes the actual content to be displayed as child elements in the layout. Finally, the ListItemCell component takes care of displaying the records. Such a Cell component covers the entire lifecycle of an asynchronous component that loads data from the server. To do this, you define various components with the names Loading, Empty, Failure, and Success. These cover the loading state, an empty result, an error while loading, and the successful loading of the data. For our example, we will focus on the Success component. This takes care of the list display and must now be extended to include a way to clear the data. Listing 3 contains the source code of the ListItemCell.

Listing 3: Delete routine in ListItemCell component

import { useMutation } from '@redwoodjs/web';


export const QUERY = gql`
  query ADDRESSES {
    addresses {
      id
      firstname
      lastname
      street
      city
    }
  }
`;


const DELETE_ADDRESS_MUTATION = gql`
  mutation DeleteAddressMutation($id: Int!) {
    deleteAddress(id: $id) {
      id
    }
  }
`;
...
export const Success = ({ addresses }) => {
  const [deleteAddress] = useMutation(DELETE_ADDRESS_MUTATION, {
    refetchQueries: [{ query: QUERY }],
    awaitRefetchQueries: true,
  });


  const handleDelete = (address) => {
    if (confirm(`Are you sure to delete the address of ${address.firstname} ${address.lastname}?`)) {
      deleteAddress({ variables: { id: address.id } });
    }
  };


  return (
    <tbody>
      {addresses.map((address) => (
        <tr key={address.id}>
          <td>{address.firstname}</td>
          <td>{address.lastname}</td>
          <td>{address.street}</td>
          <td>{address.city}</td>
          <td>
            <button onClick={() => handleDelete(address)}>delete</button>
          </td>
        </tr>
      ))}
    </tbody>
  );
}

The component loops through all records and displays them. The last cell of each row contains a button element to delete the respective record. The click event is connected to the handleDelete function, to which you pass the Address object to be deleted. You implement the handleDelete function inside the Success component and use the confirm function to ask the users if they really want to delete the record. If this is confirmed, execute the deleteAddress function. This function triggers the deleteAddress mutation. To create this function, use the useMutation hook. This comes from Apollo, the GraphQL client that Redwood uses. Redwood encapsulates this hook function and makes it available to you. To the useMutation function, you pass the mutation you created using the gql function. As a further option, you pass an object with the properties refetchQueries and awaitRefetchQueries. These ensure that the list query data is reloaded and updated after a successful delete operation. Redwood also waits for the data to be loaded until the delete operation is marked as done.

If you start your application in development mode with the yarn rw dev command, the webpack dev server works in the background and ensures that changes to the source code in the browser take effect immediately. If you press the delete button in your application’s browser and confirm the displayed message, the browser sends a GraphQL request to the backend and executes the delete mutation, which deletes the record in the database. If the browser receives the corresponding success message, the list is fetched from the server again and displayed without the deleted record. Now that you can view and delete records, the next step should be to create new records and edit existing ones.

Routing

An important Redwood feature that we have hardly looked at so far is routing, i.e. navigation in an application. When creating a page component, you can specify which route this component should be connected to. For the list, this was the / route, the entry path to the application. For creating and editing you need two more routes:

  • /create: create new records
  • /edit/{id:Int}: modify existing records

Let's first look at the simpler variant: creating new records. Use the yarn rw g page create command to create a new page component with the name CreatePage. In this case, Redwood automatically registers the /create route, so you don't have to worry about anything else. When editing records you must also pass the information about which record you want to edit in addition to the static path. This is done via route parameters. For example, if you want to edit the record with ID 42, the path is / edit/42. To achieve this, you have several options. You can use the yarn rw g page edit {id} command to specify that a parameter named id should be inserted in addition to the path. By using the yarn g page command, the Redwood CLI has created three files each for you:

  • Page.js: The Page component itself, which is rendered when the user activates the corresponding path.
  • Page.test.js: This file contains an initial simple test for the component that ensures that no exception is thrown when the component is rendered.
  • Page.stories.js: Redwood includes Storybook support by default and creates a Storybook file for each component, through which you can view the component in isolation.

As an alternative to specifying the routing parameter directly in the command, you can simply issue the yarn rw g page edit command and modify the Routes.js file located in the src directory directly. Listing 4 shows how this file should look after integrating the two additional routes.

Listing 4: The Routes.js file with all available routes

import { Router, Route } from '@redwoodjs/router'


const Routes = () => {
  return (
    <Router>
      <Route path="/create" page={CreatePage} name="create" />
      <Route path="/edit/{id:Int}" page={EditPage} name="edit" />
      <Route path="/" page={ListPage} name="list" />
      <Route notfound page={NotFoundPage} />
    </Router>
  )
}


export default Routes

For navigation within an application, Redwood uses its own router. This is based on the React router, but is a custom implementation of the Redwood team and is managed in the @redwoodjs/router package. The router is configured declaratively via components, similar to the React router. The router component includes the entire configuration. You define the individual routes with the help of the Route component. It gets a path, a page, and a name as props. The path specifies which URL path you want to use to activate the route. The page prop is used to pass the page component that you want to render for the URL path. You use the name prop to create URLs for links to the respective route. To make the procedure for this a little clearer, in the next step we add a reference from the List page to the new Create page. Listing 5 shows the source code required for this.

Listing 5: Create links with the Redwood Router

import { Link, routes } from '@redwoodjs/router'
...
<Link to={routes.create()}>
  <button>new</button>
</Link>

For such a link between the pages of your application, import the link component and the routes object from the @redwoodjs/router package. For the link component, define the to prop to indicate which page you want to link to. This is where the value of the name prop of the route comes into play. If you specify a name when defining a route, a so-called Named Route Function is automatically generated by the Redwood Router. This function creates the URL to the route, so that if you change the path, you only have to change the route configuration and not the rest of your application. You access the Named Route Functions via the routes object that you have just imported. If your users now click on the button, the (currently still empty) Create...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK