diff --git a/Databases/GraphQL/Apollo/Apollo_Client.md b/Databases/GraphQL/Apollo/Apollo_Client.md index 358068f..cbab9bb 100644 --- a/Databases/GraphQL/Apollo/Apollo_Client.md +++ b/Databases/GraphQL/Apollo/Apollo_Client.md @@ -9,9 +9,11 @@ tags: [graph-ql, apollo] Apollo Client is the client-side counterpart to [Apollo Server](/Databases/GraphQL/Apollo/Apollo_Server.md). We use it for managing queries and mutations from the frontend to our Apollo GraphQL server. It is specifically designed to work with React. +> We will be working with the [schema](/Databases/GraphQL/Apollo/Apollo_Server.md#example-schema) we defined when working on the server + ## Initializing the client -We initialise the client and set-up in memory caching to reduce network requests: +We initialise the client and set-up in memory kcaching to reduce network requests: ```js const client = new ApolloClient({ @@ -62,6 +64,7 @@ const TRACKS = gql` ``` The convention is to name the query constant in `ALL_CAPS`. + > Note that the name of the query on the client doesn't have to match the query type defined in the schema however it should reference it on the second line (`tracksFormHome) ### `useQuery` hook @@ -87,4 +90,3 @@ const Tracks = () => { - We destructure the `loading, error, data` variables that are returned from the hook - We pass in our query constant as an argument. - In the example we just render the serialized data but we could of course pass the data as a prop and map through it in an embedded child component. - diff --git a/Databases/GraphQL/Apollo/Apollo_Server.md b/Databases/GraphQL/Apollo/Apollo_Server.md index 05ebbc6..a372318 100644 --- a/Databases/GraphQL/Apollo/Apollo_Server.md +++ b/Databases/GraphQL/Apollo/Apollo_Server.md @@ -7,19 +7,21 @@ tags: [graph-ql, apollo] # Apollo Server -> Apollo Server is the part of the Apollo suite that we use to create the backend of a GraphQL project; a GraphQL server. +> Apollo Server is the part of the Apollo suite that we use to create the backend of a GraphQL project: a GraphQL server. It is able to do the following: - Receive an incoming GraphQL query from a client - Validate that query against the server schema -- Populate the queried schema fields -- Return the fields as a response +- Populate the queried schema fieldsj +- Return the fields as a JSON response object ## Example schema We will use the following schema in the examples +// + ```js const typeDefs = gql` type Query { @@ -27,6 +29,7 @@ const typeDefs = gql` tracksForHome: [Track!]! } +// TODO, rewrite this so I can get syntax highlighting "A track is a group of Modules that teaches about a specific topic" type Track { id: ID! @@ -56,6 +59,8 @@ module.exports = typeDefs; ## Setting up the server +We instantiate an `ApolloServer` instance and pass our schema to it. We then subscribe to it with a [listener](/Programming_Languages/Node/Modules/Core/Node_JS_events_module.md#extending-the-eventemitter-class). + ```js const { ApolloServer } = require("apollo-server"); const typeDefs = require("./schema"); @@ -111,35 +116,131 @@ const mocks = { const server = new ApolloServer({ typeDefs, mocks }); ``` -We can now [run queries](/Databases/GraphQL/Apollo/Apollo_Client.md#running-a-query) against our server. + +We can now [run queries](/Databases/GraphQL/Apollo/Apollo_Client.md#running-a-query) against our server. ## Implementing resolvers -A resolver is a [function](/Databases/GraphQL/Creating_a_GraphQL_server.md#resolvers) +A resolver is a [function](/Databases/GraphQL/Creating_a_GraphQL_server.md#resolvers) that populates data for a given query. It should have **the same name as the field for the query**. So far we have one query in our schema: `tracksForHome` which returns the tracks to be listed on the home page. We must therefore we must also name our resolver for this query `tracksForHome`. + +It can fetch data from any data source or multiple data sources (other servers, databases, REST APIs) and then presents this as a single integrated resouce to the client, matching the shape requested. + +As per the [generic example](/Databases/GraphQL/Creating_a_GraphQL_server.md#resolvers), you write write your resolvers as keys on a `resolvers` object, e.g: + +```js +const resolvers = {}; +``` + +The `resolvers` object's keys will correspond to the schema's types and fields. For some reason Apollo requires extra scaffolding around the keys, you have to wrap the key in `Query` like so: + +```js +const resolvers = { + Query: { + tracksForHome: () => {}, + }, +}; +``` + +### Resolver parameters + +The resolver function has standard parameters that you draw on when implementing the resolution: + +- `parent` + - something to do with resolver chains //TODO: return to +- `args` + - an object containing the argments that were provided for the field by the client. For instance if the client requests a field with an accompanying `id` argument, `id` will show up in the `args` object +- `context` + - shared state between different resolvers that contains essential connection parameters such as authentication, a database connection, or a `RESTDataSource` (see below) +- `info` + - least essential, used for caching + +Typically you won't use every parameter with every resolver. You can ommit them with `_, __`; the number of dashes indicating the argument placement. ### `RESTDataSource` -This is something you can apply to your server to improve the efficiency of working with REST APIs in your resolvers. +This is something you can apply to your server to improve the efficiency of working with REST APIs in your resolvers. -REST APIs fall victim to the "n + 1" problem: say you want to get one resource, then for each element returned of this resource you need to send another request using a property of this resource to get another resource. For each of the first requests you need to send another request once they resolve. +REST APIs fall victim to the "n + 1" problem: say you want to get an array of one resource type, then for each element returned you need to send another request using one of its properties to fetch a related resource. + +This is implicit in the case of the `Track` type in the schema. Each `Track` has an `author` key but the `Author` type isn't embedded in `Track` it has to be fetched using an `id`. In a REST API, this would require a request to +a separate end point for each `Track` returned. Here is an example of `RESTDataSource` being used. It is just a class that can be extended and which provides inbuilt methods for running fetches against a REST API: ```js -const {RESTDataSource} = require('apollo-datasource-rest'); +const { RESTDataSource } = require("apollo-datasource-rest"); class TrackAPI extends RESTDataSource { constructor() { super(); - this.baseURL = 'https://odyssey-lift-off-rest-api.herokuapp.com/'; + this.baseURL = "https://odyssey-lift-off-rest-api.herokuapp.com/"; } getTracksForHome() { - return this.get('tracks'); + return this.get("tracks"); } getAuthor(authorId) { return this.get(`author/${authorId}`); } } -``` \ No newline at end of file +``` + +### Using our `RESTDataSource` in our resolver + +As our GraphQL server is sourcing data from a REST API, we can now integrate the `RESTDataSource` class with our resolver. + +First thing, we need to instantiate an instance of our `TrackApi` class, otherwise we won't be able to use any of its methods in the resolver. + +We will create an instance of this class and pass it into `ApolloServer` object we established at the beginning. We will pass it to the `dataSources` key. **This will allow us to access it from within the `context` parameter in our resolver function** + +We can also get rid of the `mocks` object since we don't need it any more. We will replace it with our `resolvers` constant: + +```diff +const server = new ApolloServer({ + typeDefs, +- mocks, ++ resolvers, ++ dataSources: () => { ++ return { ++ trackApi: new TrackApi() ++ } + } +}) +``` + +Now we can complete our resolver: + +```js +const resolvers = { + Query: { + tracksForHome: (_, __, {dataSources}) => {}, + return dataSources.trackApi.getTracksForHome() + }, +}; +``` + +So we destructure the `dataSources` object from the parent Apollo Server instance (in the place of the `context` parameter) which gives us access to our `trackApi` class. This resolver will now make the API request and return the tracks. + +The `tracksForHome` query returns `Track` objects and these have a required `author` key that returns an `Author` type. So we are also going to need a resolver that can return the author data that will be populated along with `Track`. + +We already have this functionality in our class: `getAuthor` so we just need to integrate it: + +```js +const resolvers = { + Query: { + tracksForHome: (_, __, { dataSources }) => { + return dataSources.trackApi.getTracksForHome(); + }, + }, + Track: { + author: ({ authorId }, _, { dataSources }) => { + return dataSources.trackApi.getAuthor(authorId); + }, + }, +}; +``` + +- We keep `Track` outside of `Query` because it has no corresponding query in the schema and we must always match the schema. `Track` is a self-standing field so the resolver must **match this schema shape**. The query `getTracksForHome` references `Track` but it is a separate field. +- We invoke the `context` again when we destructure `dataSources`. +- This time we utilise the `args` parameter in the resolver since an `id` will be provided from the client to return a specific author. diff --git a/Databases/GraphQL/Apollo/Using_arguments_with_Apollo_Client.md b/Databases/GraphQL/Apollo/Using_arguments_with_Apollo_Client.md new file mode 100644 index 0000000..3abe3d1 --- /dev/null +++ b/Databases/GraphQL/Apollo/Using_arguments_with_Apollo_Client.md @@ -0,0 +1,44 @@ +--- +title: Using arguments with Apollo Client +categories: + - Databases +tags: [graph-ql, apollo] +--- + +# Using arguments with Apollo Client + +In order to demonstrate arguments we need to expand the [original schema](/Databases/GraphQL/Apollo/Apollo_Server.md#example-schema). + +Remember a Track is a group of Modules that teaches about a specific topic. We are going to add: + +- `description` and `numberOfViews` fields to the original `Track` type +- A new `Module` type +- A field `modules` to the `Track` type that will be an array of type `Module` + +## Updated schema + +```js +type Track { + id: ID! + title: String! + author: Author! + thumbnail: String + length: Int + modulesCount: Int + description: String + numberOfViews: Int + modules: [Module!]! +} + +type Module { + id: ID! + title: String! + length: Int +} + +type Author { + id: ID! + name: String! + photo: String + } +``` diff --git a/Databases/GraphQL/Creating_a_GraphQL_server.md b/Databases/GraphQL/Creating_a_GraphQL_server.md index aa6ae99..4bd6b4b 100644 --- a/Databases/GraphQL/Creating_a_GraphQL_server.md +++ b/Databases/GraphQL/Creating_a_GraphQL_server.md @@ -50,8 +50,7 @@ app.use( ## Resolvers - -We will specify our resolvers in a dedicate resolver file. In GraphQL you need to define resolvers for both your queries and your mutations. +We will specify our resolvers in a dedicated resolver file. In GraphQL you need to define resolvers for both your queries and your mutations. To achieve this we will have a dummy object as the database containing our products and a class working as a generator function that will create a product object with certain properties, individuated by an id. We will invoke this class to create new products for the database and to retrieve existing products from the database. diff --git a/Databases/MongoDB/Adding_documents_to_a_collection.md b/Databases/MongoDB/Adding_documents_to_a_collection.md index dd2e5a1..469e24c 100644 --- a/Databases/MongoDB/Adding_documents_to_a_collection.md +++ b/Databases/MongoDB/Adding_documents_to_a_collection.md @@ -1,7 +1,7 @@ --- categories: - Databases -tags: [mongo_db, node_js, mongoose] +tags: [mongo_db, node-js, mongoose] --- # Adding documents to a collection diff --git a/Databases/MongoDB/Creating_a_schema_and_model.md b/Databases/MongoDB/Creating_a_schema_and_model.md index 2e63d6d..f753aa9 100644 --- a/Databases/MongoDB/Creating_a_schema_and_model.md +++ b/Databases/MongoDB/Creating_a_schema_and_model.md @@ -1,7 +1,7 @@ --- categories: - Databases -tags: [mongo_db, node_js, mongoose] +tags: [mongo_db, node-js, mongoose] --- # Creating a schema and model diff --git a/Databases/MongoDB/Validating_Mongoose_schemas.md b/Databases/MongoDB/Validating_Mongoose_schemas.md index 2eef459..d965fa6 100644 --- a/Databases/MongoDB/Validating_Mongoose_schemas.md +++ b/Databases/MongoDB/Validating_Mongoose_schemas.md @@ -1,7 +1,7 @@ --- categories: - Databases -tags: [mongo_db, node_js, mongoose] +tags: [mongo_db, node-js, mongoose] --- # Validating Mongoose schemas diff --git a/Programming_Languages/Node/Modules/Core/events.md b/Programming_Languages/Node/Modules/Core/Node_JS_events_module.md similarity index 84% rename from Programming_Languages/Node/Modules/Core/events.md rename to Programming_Languages/Node/Modules/Core/Node_JS_events_module.md index b4fb51d..656ce72 100644 --- a/Programming_Languages/Node/Modules/Core/events.md +++ b/Programming_Languages/Node/Modules/Core/Node_JS_events_module.md @@ -7,7 +7,7 @@ tags: - node-modules --- -# `events` module +# Node.js `events` module In most cases you won't interact with the `events` module directly since other modules and third-party modules are abstractions on top of it. For instance the `http` module is using events under the hood to handle requests and responses. @@ -23,14 +23,14 @@ Because Node's runtime is [event-driven](/Programming_Languages/NodeJS/Architect ## Basic syntax ```js -const EventEmitter = require('events'); // import the module +const EventEmitter = require("events"); // import the module // Raise an event -const emitter = new EventEmitter('messageLogged'); +const emitter = new EventEmitter("messageLogged"); // Register a listener -emitter.on('messagedLogged', function () { - console.log('The listener was called.'); +emitter.on("messagedLogged", function () { + console.log("The listener was called."); }); ``` @@ -45,12 +45,12 @@ emitter.on('messagedLogged', function () { ```js // Raise an event -const emitter = new EventEmitter('messageLogged', function (eventArg) { - console.log('Listener called', eventArg); +const emitter = new EventEmitter("messageLogged", function (eventArg) { + console.log("Listener called", eventArg); }); // Register a listener -emitter.on('messagedLogged', {id: 1, url: 'http://www.example.com'}); +emitter.on("messagedLogged", { id: 1, url: "http://www.example.com" }); ``` ## Extending the `EventEmitter` class @@ -61,12 +61,12 @@ emitter.on('messagedLogged', {id: 1, url: 'http://www.example.com'}); ```js // File: Logger.js -const EventEmitter = require('events'); +const EventEmitter = require("events"); class Logger extends EventEmitter { log(message) { console.log(message); - this.emit('messageLogged', {id: 1, url: 'http://www.example.com'}); + this.emit("messageLogged", { id: 1, url: "http://www.example.com" }); } } ``` diff --git a/Resources.md b/Resources.md index 57d3468..c9e7fa6 100644 --- a/Resources.md +++ b/Resources.md @@ -1 +1,7 @@ -https://runestone.academy/ns/books/published/welcomecs/index.html +## Computer Science modules + +[Welcome to CS](https://runestone.academy/ns/books/published/welcomecs/index.html) + +## Python + +[Tiny Python Projects (O'Reilly)](https://learning.oreilly.com/library/view/tiny-python-projects/9781617297519/)