Chapter 2: Express Intro

Full Stack React Native

link Express Introduction

By the end of this lesson, developers will be able to:

  • Explain the role of a web server in a full-stack application
  • Write a route handler for a GET request with Express
  • Pass information with query and route parameters
  • Respond to a GET request with data from a database

link Overview

Express is a library we'll use to make web servers for our frontend applications to query our databases.

link Where Express fits in

When we talk about the "back end" of a web application, we're actually talking about a few things. For now, let's talk about two of those:

  • The server
  • The database

Node.js —— specifically Express, which is a Node.js framework —— allows us to build server-side applications in Javascript. And that server-side application will talk to a database.

Builder Book

We will store our information in databases. But how can we access a database from a JavaScript server-side application?

link Object Relational Mapping (ORM)

An Object Relational Mapping, or ORM, bridges this gap for us. An ORM library like Sequelize offers an easy way to communicate with a database with familiar JavaScript syntax.

Builder Book

In short, we want to be able to interact with a SQL database using Object-Oriented programming. An ORM provides a set of methods that allow developers to set up this functionality.

Introduction

We'll use Express solely as a JSON API server. Our database will contain information about writing prompts. Our React Native app will get randomized prompts from our server.

We are going to build a prompts api with limited CRUD functionality - that means our api will only have the ability to fetch a random prompt.

Let's get started!

Let's begin building our Apollo server by creating a new directory called prompts-api:

mkdir prompts-api
cd prompts-api
npm init -y

link Dependencies

Then, switch into your new directory and install the apollo-server, graphql, sequelize and pg dependencies:

cd prompts-api
yarn add sequelize pg faker && yarn add -D sequelize-cli nodemon

We will also utilize the faker package to add seed data in a few moments.

Next we will initialize a Sequelize project:

npx sequelize-cli init

Let's setup our database configuration:

prompts-api/config/config.json

{
  "development": {
    "database": "prompts_api_development",
    "dialect": "postgres"
  },
  "test": {
    "database": "prompts_api_test",
    "dialect": "postgres"
  },
  "production": {
    "use_env_variable": "DATABASE_URL",
    "dialect": "postgres",
    "dialectOptions": {
      "ssl": true
    }
  }
}

Notice: For production we use use_env_variable and DATABASE_URL. We are going to deploy this app to Heroku. Heroku is smart enough to replace DATABASE_URL with the production database. You will see this at the end of the lesson.

Cool, we should now be all setup to create the database. Let's do that:

npx sequelize-cli db:create

Next we will create a Prompt model:

npx sequelize-cli model:generate --name Prompt --attributes title:text

Checkout the Sequelize Data Types that are available: https://sequelize.org/master/manual/data-types.html

Now we need to execute our migration which will create the prompts table in our Postgres database along with the columns:

npx sequelize-cli db:migrate

If you made a mistake, you can always rollback: npx sequelize-cli db:migrate:undo

Now let's create a seed file:

npx sequelize-cli seed:generate --name prompt
Let's edit the code for the prompt seed file (**Click Me**)

'use strict';
const faker = require('faker');

const prompts = [...Array(100)].map((prompt) => (
  {
    title: faker.hacker.phrase(),
    createdAt: new Date(),
    updatedAt: new Date()
  }
))

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Prompts', prompts, {});
  },

  down: (queryInterface, Sequelize) => {
    /*
      Add reverting commands here.
      Return a promise to correctly handle asynchronicity.

      Example:
      return queryInterface.bulkDelete('People', null, {});
    */
  }
};

Seed Database

Execute the seed file:

npx sequelize-cli db:seed:all

Made a mistake? You can always undo: npx sequelize-cli db:seed:undo

Drop into psql and query the database for the demo user:

psql prompts_api_development
SELECT * FROM "Prompts";

Create a .gitignore file:

touch .gitignore

Insert the following inside .gitignore:

/node_modules
.DS_Store
.env

link GraphQL Server

Enough Sequelize! Let's modify our GraphQL server boilerplate:

const { ApolloServer, gql } = require('apollo-server') 
const { Prompts, sequelize } = require('../models');

const port = process.env.PORT || 4000;

const typeDefs = gql`
  type Prompt { title: String!, id: ID! }
  type Query { prompt: Prompt }
`;

const resolvers = {
  Query: {
    prompt: async (_, args) => {
      const prompt = await Prompts.findOne({ 
        order: sequelize.random() 
      });
      return prompt;
    },
  },
};

const server = new ApolloServer({
  typeDefs, 
  resolvers,
});

server.listen({port}, () => console.log(`Server is running at http://localhost:${port}`))

Schema Definition Language (SDL)

GraphQL schemas are now most often specified using what’s known as the GraphQL SDL (schema definition language), also sometimes referred to as just GraphQL schema language.

Our GraphQL schema defines two unique properties: typeDefs and resolvers. Let's dig into their details:

  • typeDefs define what types of data are accessible in the GraphQL server.
  • resolvers will execute logic based on the Express models.

Object Types

The most basic components of a GraphQL schema are object types, which just represent a kind of object you can fetch from your service, and what fields it has. In the GraphQL schema language, we might represent it like this:

type Prompt { 
  title: String!, 
  id: ID! 
}
type Query { 
  prompt: Prompt 
}

The language is pretty readable, but let's go over it so that we can have a shared vocabulary:

  • Prompt is a GraphQL Object Type, meaning it's a type with some fields. Most of the types in your schema will be object types.

  • title and id are fields on the Prompt type. That means that title and id are the only fields that can appear in any part of a GraphQL query that operates on the Prompt type.

  • String is one of the built-in scalar types - these are types that resolve to a single scalar object, and can't have sub-selections in the query. We'll go over scalar types more later.

  • String! means that the field is non-nullable, meaning that the GraphQL service promises to always give you a value when you query this field. In the type language we represent those with an exclamation mark.

Source

Queries

Every GraphQL service has a query type and may or may not have a mutation type. These types are the same as a regular object type, but they are special because they define the entry point of every GraphQL query.

  • Since we defined a Query type that contains a Prompt object, we can expect to fetch a prompt from our database.

Let's perform our first query in the next section.

What is a Resolver?

Resolvers provide the instructions for turning a GraphQL operation (a query, mutation, or subscription) into data. They either return the same type of data we specify in our schema or a promise for that data.

Before we can start writing resolvers, we need to learn more about what a resolver function looks like. Resolver functions accept four arguments:

fieldName: (parent, args, context, info) => data;
  • parent: An object that contains the result returned from the resolver on the parent type
  • args: An object that contains the arguments passed to the field
  • context: An object shared by all resolvers in a GraphQL operation. We use the context to contain per-request state such as authentication information and access our data sources.
  • info: Information about the execution state of the operation which should only be used in advanced cases

Source

Test Server

Modify your package.json file to support nodemon. Also, let's create a command npm db:reset that will drop the database, create the database, run the migrations, and seed!

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "nodemon src/index.js",
  "db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all"
},

Let's make sure our server works:

yarn start

Open http://localhost:4000 you should see a playground for your GraphQL endpoints.

Insert the following snippet to fetch a random prompt:

query {
  prompt {
    id
    title
  }
}

We now have an API with some CRUD functionality built in.

Well Done!

link Deployment

Let's deploy our app to heroku.

First we need to update our package.json:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "start": "node src/index.js",
  "dev": "nodemon src/index.js",
  "db:reset": "npx sequelize-cli db:drop && npx sequelize-cli db:create && npx sequelize-cli db:migrate && npx sequelize-cli db:seed:all"
},

Make sure you download the heroku cli and run heroku login

  1. heroku create your-heroku-app-name
  2. heroku buildpacks:set heroku/nodejs --app=your-heroku-app-name
  3. heroku addons:create heroku-postgresql:hobby-dev --app=your-heroku-app-name
  4. git status
  5. git commit -am "add any pending changes"
  6. git push heroku master
  7. heroku run npx sequelize-cli db:migrate
  8. heroku run npx sequelize-cli db:seed:all

Having issues? Debug with the Heroku command heroku logs --tail to see what's happening on the Heroku server.

Test the endpoints!

https://your-heroku-app-name.herokuapp.com

Wrapping Up

Today we made and deployed a GraphQL server which displays random prompts. The next chapter introduces queries to fetch asynchronous data.

format_list_bulleted
help_outline