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.
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.
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
andDATABASE_URL
. We are going to deploy this app to Heroku. Heroku is smart enough to replaceDATABASE_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
andid
are fields on thePrompt
type. That means that title and id are the only fields that can appear in any part of a GraphQL query that operates on thePrompt
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.
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 aPrompt
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 typeargs
: An object that contains the arguments passed to the fieldcontext
: 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
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 runheroku login
heroku create your-heroku-app-name
heroku buildpacks:set heroku/nodejs --app=your-heroku-app-name
heroku addons:create heroku-postgresql:hobby-dev --app=your-heroku-app-name
git status
git commit -am "add any pending changes"
git push heroku master
heroku run npx sequelize-cli db:migrate
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!
Wrapping Up
Today we made and deployed a GraphQL server which displays random prompts. The next chapter introduces queries to fetch asynchronous data.