├── .gitignore ├── package.json ├── src ├── index.js ├── datasources │ └── track-api.js ├── resolvers.js └── schema.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .env -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catstronauts-server-complete", 3 | "version": "1.0.0", 4 | "description": "back-end demo app for Apollo's lift-off IV course", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index", 8 | "dev": "nodemon src/index" 9 | }, 10 | "dependencies": { 11 | "apollo-datasource-rest": "^0.11.0", 12 | "apollo-server": "^3.0.0", 13 | "graphql": "^15.5.1" 14 | }, 15 | "devDependencies": { 16 | "dotenv": "^8.2.0", 17 | "nodemon": "^2.0.4" 18 | }, 19 | "author": "Raphael Terrier @R4ph-t", 20 | "license": "MIT", 21 | "private": true 22 | } 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('apollo-server'); 2 | const typeDefs = require('./schema'); 3 | const resolvers = require('./resolvers'); 4 | const TrackAPI = require('./datasources/track-api'); 5 | 6 | async function startApolloServer(typeDefs, resolvers) { 7 | const server = new ApolloServer({ 8 | typeDefs, 9 | resolvers, 10 | dataSources: () => { 11 | return { 12 | trackAPI: new TrackAPI(), 13 | }; 14 | }, 15 | }); 16 | 17 | const { url, port } = await server.listen({port: process.env.PORT || 4000}); 18 | 19 | console.log(` 20 | 🚀 Server is running 21 | 🔉 Listening on port ${port} 22 | 📭 Query at ${url} 23 | `); 24 | } 25 | 26 | startApolloServer(typeDefs, resolvers); 27 | -------------------------------------------------------------------------------- /src/datasources/track-api.js: -------------------------------------------------------------------------------- 1 | const { RESTDataSource } = require('apollo-datasource-rest'); 2 | 3 | class TrackAPI extends RESTDataSource { 4 | constructor() { 5 | super(); 6 | // the Catstronauts catalog is hosted on this server 7 | this.baseURL = 'https://odyssey-lift-off-rest-api.herokuapp.com/'; 8 | } 9 | 10 | getTracksForHome() { 11 | return this.get('tracks'); 12 | } 13 | 14 | getAuthor(authorId) { 15 | return this.get(`author/${authorId}`); 16 | } 17 | 18 | getTrack(trackId) { 19 | return this.get(`track/${trackId}`); 20 | } 21 | 22 | getTrackModules(trackId) { 23 | return this.get(`track/${trackId}/modules`); 24 | } 25 | 26 | getModule(moduleId) { 27 | return this.get(`module/${moduleId}`); 28 | } 29 | 30 | incrementTrackViews(trackId) { 31 | return this.patch(`track/${trackId}/numberOfViews`); 32 | } 33 | } 34 | 35 | module.exports = TrackAPI; 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Odyssey Lift-off V (Server): Road to production 2 | 3 | Welcome to the companion app of Odyssey Lift-off V (server)! You can [find the course lessons and instructions on Odyssey](https://odyssey.apollographql.com/lift-off-part5), Apollo's learning platform. 4 | 5 | You can [preview the completed demo app here](https://lift-off-client-demo.netlify.app/). 6 | 7 | You can [find the client counterpart here](https://github.com/apollographql/odyssey-lift-off-part5-client). 8 | 9 | ## How to use this repo 10 | 11 | The course will walk you step by step on how to implement the features you see in the demo app. This codebase is the starting point of your journey! 12 | 13 | This repo is the starting point of our GraphQL server. 14 | 15 | To get started: 16 | 17 | 1. Run `npm install`. 18 | 1. Run `npm start`. 19 | 20 | This will start the GraphQL API server. 21 | 22 | To check the **final** stage of the server, with all of the steps and code completed, checkout the `final` branch by running the following command in your terminal: 23 | 24 | ```bash 25 | git checkout final 26 | ``` 27 | 28 | ## Getting Help 29 | 30 | For any issues or problems concerning the course content, please refer to the [Odyssey topic in our community forums](https://community.apollographql.com/tags/c/help/6/odyssey). 31 | -------------------------------------------------------------------------------- /src/resolvers.js: -------------------------------------------------------------------------------- 1 | const resolvers = { 2 | Query: { 3 | // returns an array of Tracks that will be used to populate the homepage grid of our web client 4 | tracksForHome: (_, __, { dataSources }) => { 5 | return dataSources.trackAPI.getTracksForHome(); 6 | }, 7 | 8 | // get a single track by ID, for the track page 9 | track: (_, { id }, { dataSources }) => { 10 | return dataSources.trackAPI.getTrack(id); 11 | }, 12 | 13 | // get a single module by ID, for the module detail page 14 | module: (_, { id }, { dataSources }) => { 15 | return dataSources.trackAPI.getModule(id); 16 | }, 17 | }, 18 | Mutation: { 19 | // increments a track's numberOfViews property 20 | incrementTrackViews: async (_, { id }, { dataSources }) => { 21 | try { 22 | const track = await dataSources.trackAPI.incrementTrackViews(id); 23 | return { 24 | code: 200, 25 | success: true, 26 | message: `Successfully incremented number of views for track ${id}`, 27 | track, 28 | }; 29 | } catch (err) { 30 | return { 31 | code: err.extensions.response.status, 32 | success: false, 33 | message: err.extensions.response.body, 34 | track: null, 35 | }; 36 | } 37 | }, 38 | }, 39 | Track: { 40 | author: ({ authorId }, _, { dataSources }) => { 41 | return dataSources.trackAPI.getAuthor(authorId); 42 | }, 43 | 44 | modules: ({ id }, _, { dataSources }) => { 45 | return dataSources.trackAPI.getTrackModules(id); 46 | }, 47 | }, 48 | }; 49 | 50 | module.exports = resolvers; 51 | -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('apollo-server'); 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | "Query to get tracks array for the homepage grid" 6 | tracksForHome: [Track!]! 7 | "Fetch a specific track, provided a track's ID" 8 | track(id: ID!): Track! 9 | "Fetch a specific module, provided a module's ID" 10 | module(id: ID!): Module! 11 | } 12 | 13 | type Mutation { 14 | "Increment the number of views of a given track, when the track card is clicked" 15 | incrementTrackViews(id: ID!): IncrementTrackViewsResponse! 16 | } 17 | 18 | type IncrementTrackViewsResponse { 19 | "Similar to HTTP status code, represents the status of the mutation" 20 | code: Int! 21 | "Indicates whether the mutation was successful" 22 | success: Boolean! 23 | "Human-readable message for the UI" 24 | message: String! 25 | "Newly updated track after a successful mutation" 26 | track: Track 27 | } 28 | 29 | "A track is a group of Modules that teaches about a specific topic" 30 | type Track { 31 | id: ID! 32 | "The track's title" 33 | title: String! 34 | "The track's main Author" 35 | author: Author! 36 | "The track's illustration to display in track card or track page detail" 37 | thumbnail: String 38 | "The track's approximate length to complete, in minutes" 39 | length: Int 40 | "The number of modules this track contains" 41 | modulesCount: Int 42 | "The track's complete description, can be in markdown format" 43 | description: String 44 | "The number of times a track has been viewed" 45 | numberOfViews: Int 46 | "The track's complete array of Modules" 47 | modules: [Module!]! 48 | } 49 | 50 | "Author of a complete Track or a Module" 51 | type Author { 52 | id: ID! 53 | "Author's first and last name" 54 | name: String! 55 | "Author's profile picture" 56 | photo: String 57 | } 58 | 59 | "A Module is a single unit of teaching. Multiple Modules compose a Track" 60 | type Module { 61 | id: ID! 62 | "The module's title" 63 | title: String! 64 | "The module's length in minutes" 65 | length: Int 66 | "The module's text-based description, can be in markdown format. In case of a video, it will be the enriched transcript" 67 | content: String 68 | "The module's video url, for video-based modules" 69 | videoUrl: String 70 | } 71 | `; 72 | 73 | module.exports = typeDefs; 74 | --------------------------------------------------------------------------------