Chapter 5: Bitcoins API
Full Stack React Native
link Bitcoins API
By the end of this lesson, developers will be able to:
- Structure full stack applications
- Create a GraphQL API with pagination
Express API Structure
We are going to build a prompts API with limited CRUD functionality - that means our API will only have the ability to fetch bitcoins.
Let's get started!
Let's begin building our Apollo server by creating a new directory called bitcoins-api
:
mkdir bitcoins-api
cd bitcoins-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:
bitcoins-api/config/config.json
{
"development": {
"database": "bitcoins_api_development",
"dialect": "postgres"
},
"test": {
"database": "bitcoins_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 Bitcoins model:
npx sequelize-cli model:generate --name Bitcoins --attributes name:string,symbol:string,price:string,imageUrl:string,favorite:boolean
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 bitcoins 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 bitcoins
Let's edit the code for the bitcoins seed file
'use strict';
const faker = require('faker');
const bitcoins = [...Array(100)].map((bitcoin) => (
{
name: faker.finance.currencyName(),
symbol: faker.finance.currencyCode(),
price: faker.finance.amount(),
imageUrl: "https://picsum.photos/200/200",
favorite: false,
createdAt: new Date(),
updatedAt: new Date()
}
))
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('Bitcoins', bitcoins);
},
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 bitcoins_api_development
SELECT * FROM "Bitcoins";
Create a .gitignore
file:
touch .gitignore
Insert the following inside .gitignore
:
/node_modules
.DS_Store
.env
link GraphQL Server
Let's modify our GraphQL server boilerplate:
const { ApolloServer, gql } = require('apollo-server');
const { Bitcoins, sequelize } = require('../models');
const port = process.env.PORT || 4000;
const typeDefs = `
type Bitcoin { name: String!, symbol: String!, price: String!, imageUrl: String!, favorite: Boolean }
type Query { bitcoins(offset: Int, limit: Int): [Bitcoin], favorites: [Bitcoin] }
type Mutation { addCoin(symbol: String!): Bitcoin, removeCoin(symbol: String!): Bitcoin }
`;
const mapAttributes = (model, { fieldNodes }) => {
// get the fields of the Model (columns of the table)
const columns = new Set(Object.keys(model.rawAttributes));
const requested_attributes = fieldNodes[0].selectionSet.selections.map(
({ name: { value } }) => value
);
// filter the attributes against the columns
return requested_attributes.filter(attribute => columns.has(attribute));
};
const resolvers = {
Query: {
bitcoins: async (_, { offset = 0, limit = 10 }, context, info) => {
const bitcoins = await Bitcoins.findAll({
limit,
offset,
order: [['id', 'ASC']],
attributes: mapAttributes(Bitcoins, info)
});
return bitcoins;
},
favorites: async (parent, args, context, info) => {
const bitcoins = await Bitcoins.findAll({
where: { favorite: true },
attributes: mapAttributes(Bitcoins, info)
});
return bitcoins;
}
},
Mutation: {
addCoin: async (_, { symbol }, context, info) => {
const [updated] = await Bitcoins.update(
{ favorite: true },
{
where: { symbol: symbol }
}
);
if (updated) {
const updatedCoin = await Bitcoins.findOne({
where: { symbol: symbol },
attributes: mapAttributes(Bitcoins, info)
});
return updatedCoin;
}
throw new Error('Bitcoin not updated');
},
removeCoin: async (_, { symbol }, context, info) => {
const [updated] = await Bitcoins.update(
{ favorite: false },
{
where: { symbol: symbol }
}
);
if (updated) {
const updatedCoin = await Bitcoins.findOne({
where: { symbol: symbol },
attributes: mapAttributes(Bitcoins, info)
});
return updatedCoin;
}
throw new Error('Bitcoin not updated');
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
server.listen({port}, () => console.log(`Server running at http://localhost:${port}`))
Mutations
Since we defined a
Mutation
type that contains aBitcoin
object, we can expect to mutate bitcoins from our database.The
addCoin
andremoveCoin
mutations return an updatedBitcoin
.
Let's perform our first mutation in the next section.
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, and paste in the following snippet:
query {
bitcoins {
name
symbol
price
}
}
Now let's test our first mutation:
mutation AddCoin($symbol:String!) {
addCoin(symbol: $symbol) {
name
symbol
price
imageUrl
favorite
}
}
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 paginated coins. The next chapter continues with the bitcoin app's network requests.