Outils pour utilisateurs

Outils du site


issue152:c_c

This month marks the final Command & Conquer article I’ll be writing. For more details on why, you may want to look at last month’s article. That being said, I wanted to do something a little different for the last article. The first part of the article will be dedicated to some articles I’m most proud of having written, and the second half will be dedicated to writing a GraphQL API to track my Go games. So if you’re interested in one but not the other, you know where to jump to.

L'article Command & Conquer de ci mois-ci sera le dernier que j'écrirai. Pour des détails supplémentaires sur les raisons de cela, vous pouvez lire l'article du mois dernier. Cela dit, je voulais faire quelque chose d'un peu différent dans ce dernier numéro. La première partie de ce texte sera consacrée à quelques articles que je suis le plus fier d'avoir écrit, et l'autre moitié sera dédiée à l'écriture d'une API GraphQL pour suivre mes jeux de Go. Aussi, si vous êtes intéressé à l'un, mais pas à l'autre, vous savez où aller.

Part 1 I’ve been writing for FCM since issue #21 - 131 issues ago! Over that time I’ve written some articles that are, as of now, obsolete, and some that hold up to the test of time. Below you’ll find a list of my favorite articles that I’ve written, and what issue they appeared in: • CLI Cookbook - FCM #76. I’m most proud of this one because we managed to get the community involved and actually created something together. I can’t guarantee that all the commands are still accurate, but I’m sure there are still a good few ideas that are valid. The actual PDF/LaTeX documents can be found here: https://github.com/lswest/cli-cookbook

Première partie

J'écris pour le FCM depuis le n° 21 - il y a 131 numéros ! Pendant tout ce temps, j'ai écrit des articles qui sont, à l'heure actuelle, obsolètes, et certains qui ont survécu au passage du temps. Ci-dessous, vous trouverez une liste des articles préférés que j'ai écrits et dans quels numéros ils sont parus : ••CLI Cookbook - n° 76 du FCM . C'est de celui-là dont je suis le plus fier parce que nous avons réussi à y associer la communauté pour vraiment créer quelque chose ensemble. Je ne peux pas assurer que toutes les commandes soient encore exactes, mais je suis sûr qu'il y a encore quelques bonnes idées qui restent valables. Les vrais documents PDF/LaTeX peuvent être trouvés ici : https://github.com/lswest/cli-cookbook

• Flexbox Stylus - FCM #92. This was another fun little project I wrote for myself that yielded a great article. I built a set of helper functions for Stylus to easily create/manage Flexbox settings. Not terribly useful in this day and age, but still fun. • Tailwind CSS - FCM #134. This article introduced my readers to a tool that completely changed my approach to designing and styling websites, and is a method I still use to this day. Definitely a worthwhile read to anyone who’s interested in web development.

••Flexbox Stylus - n° 92 du FCM. C'était un autre petit projet sympathique, que j'ai écrit pour moi, qui est devenu un superbe article. J'ai construit un jeu de fonctions d'aide pour Stylus afin d'écrire/gérer facilement les paramètres de Flexbox. Plus tellement utile aujourd'hui, mais toujours amusant. ••Tailwind CSS - n° 134 du FCM. Cet article présentait à mes lecteurs un outil qui avait complètement changé mon approche à la conception et au style de mes sites Web, et j'utilise encore cette méthode aujourd'hui. Ça vaut vraiment la peine d'être lu par toute personne intéressée par le développement Web.

• My web development articles. I won’t list all the issues I had web development focused articles in (though there will be a few at the end of this item). The reason I’m proud of these articles is quite simple - I both enjoyed the topic, and used the knowledge in my professional life (I still do!). In writing those sorts of articles, I always hoped to make the entry into new web technologies easier for beginners. Noteworthy articles: Gatsby Multi-Language (151), AMP (127), CSS Grids (125), Static Site Generation (103). There are other articles on a wide range of topics - guitar, note taking, virtualization, etc. Unfortunately, I don’t have a complete list of articles anywhere for easy browsing. If any readers have something like that, they’re welcome to email it to me (address below).

•• Mes articles sur le développement Web. Je ne voudrais pas lister tous les numéros dans lesquels je me suis concentré sur le développement Web (bien qu'il y en ait quelques-uns à la fin de ce sujet). La raison pour laquelle je suis fier de ces articles est plutôt simple : j'ai apprécié le sujet en même temps que j'ai utilisé ces connaissances dans ma vie professionnelle (je continue encore !). En écrivant ces genres d'articles, j'ai toujours espéré rendre l'entrée dans le monde des nouvelles technologies Web plus facile pour les débutants. Des articles de valeur : Multi-langage avec Gatsby (151), AMP (127), grilles CSS (125), génération d'un site statique (103).

Il y a d'autres articles sur une large étendue de sujets : guitare, prise de note, virtualisation, etc. Malheureusement, je n'ai pas de liste complète des articles quelque part pour une recherche aisée. Si un lecteur a quelque chose comme cela, j'apprécierais qu'il me l'envoie par mail (mon adresse ci-dessous). [Ndt : Lecteurs francophones, utilisez la liste des sommaires à l'accueil du site.]

Part 2 Now, on to other topics near and dear to my heart: Go & GraphQL. For anyone not familiar with Go, it’s an ancient chinese board game (estimated at over 2500 years old), played with black and white stones on a 19×19 grid. It’s also known as Baduk or Weiqi in Korea and China, respectively.

Seconde partie

Et maintenant, passons à d'autres sujets qui me sont proches et chers à mon cœur : le Go et GraphQL.

Pour ceux qui ne connaissent pas le Go, c'est un jeu chinois ancien sur plateau (on l'estime vieux de plus de 2 500 ans environ), qui se joue avec des pierres noires et blanches sur une grille de 19×19. Il est aussi appelé respectivement Baduk ou Weigi en Corée et en Chine.

GraphQL is a (much) more recent invention. It’s a query language for APIs that define a schema of data, and allow flexible querying for information. Basic example - you could define a schema for a book and an author, and keep track of things like ISBN, number of pages, publishing date, author, title, etc. Anyone who has access to the API can, using the same URL, selectively query only the data they want (i.e. title, author, and cover page) instead of getting everything back every time. It’s the backend to Gatsby’s static site generation (controlled via the gatsby-node.js file), and is extremely powerful. Ever since using it for the first time, I’ve wanted to create my own GraphQL API to replace my aging Ruby on Rails application that I use for tracking movies and video games I want to see/buy. I have since converted the information I already had (stored in a sqlite database from Rails) into mongodb, and written the API to the point where it can access and create entries in the database. Now it’s time to expand the functionality - adding in my Go games. I will not be covering the frontend aspect (planned to be a Gatsby PWA that hydrates data on load), as it’s not been completed yet, and GraphQL is flexible enough that you can access it from pretty much anything.

GraphQL est une invention (beaucoup) plus récente. C'est un langage de requête pour les API qui définit un schéma de données et permet un requêtage flexible des informations. Un exemple simple : vous pouvez définir un schéma pour un livre et un auteur, et conserver la trace de choses comme l'ISBN, le nombre de pages, la date de publication, l'auteur, le titre, etc. Quiconque a accès à l'API peut, en utilisant la même URL, requérir uniquement les données qu'il veut (par ex., le titre, l'auteur et la page de couverture) au lieu de tout récupérer à chaque fois. C'est l'arrière-boutique de la génération de site statique avec Gatsby (pilotée via le fichier gatsby-node.js) et c'est extrêmement puissant. À tel point que, après l'avoir utilisé pour la première fois, j'ai voulu créer ma propre API GraphQL pour remplacer mon application Ruby on Rails vieillissante que j'utilise pour suivre les films et les jeux vidéos que je veux voir/acheter. Depuis, j'ai converti les informations que j'avais déjà (conservées dans une base de données sqlite en Rails) en mongodb, et écrit l'API jusqu'au point où je peux accéder et créer des entrées dans la base de données. Maintenant, il est temps d'étendre la fonctionnalité, en y ajoutant mes jeux de Go. Je ne couvrirai pas l'aspect de présentation (prévu d'être un PWA Gatsby qui hydrate les données au chargement), car il n'est pas encore terminé et GraphQL est suffisamment flexible pour que vous puissiez y accéder à partir de pratiquement tout.

All code has been placed into a Gist here: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2 I will be linking to individual files throughout the article! So there’s no need to grab them all now.

Tout le code a été mis dans un Gist ici : https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2

Au long de l'article, je ferai le lien avec des fichiers spécifiques ! Aussi, il n'y a pas besoin de tous les récupérer pour le moment.

The Basics I set up my API using Express.js, mongoose, apollo-server, and apollo-server-express. Most aspects will remain the same regardless of implementation, but the actual connection to the database will differ. How does a GraphQL API work? You define a few schemas (think of it as a class definition) for queries, types, and mutations. Mutations are the create/update/delete aspect of CRUD, and queries are the “read” aspect. I won’t go into detail on the mutations, just a basic create function.

Les bases

J'ai paramétré mon API en utilisant Express.js, mongoose, apollo-server et apollo-server-express. La plupart des points ne changeront pas suivant l'implémentation, mais la connexion réelle à la base de données différera.

Comment fonctionne l'API GraphQL ? Vous définissez quelques schémas (voyez-les comme une définition de classe) pour les requêtes, les types et les mutations. Les mutations sont le côté créer/mettre à jour/effacer de CRUD et les requêtes sont l'aspect « lecture ». Je ne rentrerai pas dans le détail des mutations et ne parlerai qu'une fonction créer de base.

GraphQL then takes your defined schema and uses it for validation, typing, and for understanding the requests sent to it. The schemas also control which fields from your database are available in the API. Basic folder structure: /src/models/ /src/schemas/ /src/resolvers/ /src/index.js /package.json

GraphQL prend ensuite le schéma défini et l'utilise pour la validation, le typage et pour comprendre les demandes qui lui sont envoyées. Les schémas contrôlent aussi les champs de la base de données qui sont disponibles dans l'API.

Structure de base des dossiers : /src/models/ /src/schemas/ /src/resolvers/ /src/index.js /package.json

Requirements Make sure you’ve installed NodeJS (the LTS should be sufficient if you don’t want to be on the faster moving stable branch), mongodb (or your database system of choice), and have some test data prepared (for example a JSON block to import into mongodb or to hard-code into the app). To get the project up and running, you can do the following (if you prefer npm, all yarn commands have npm equivalents): yarn init yarn add -D nodemon @babel/core @babel/node @babel/preset-env

Requis

Asssurez-vous d'avoir installé NodeJS (la LTS devrait être suffisante si vous ne souhaitez pas être sur la branche stable évoluant plus rapidement), mongodb (ou le système de base de données de votre choix) et d'avoir préparé quelques jeux de test de données (par exemple un bloc JSON pour l'importer dans mongodb ou pour le coder en dur dans l'appli).

Pour installer et lancer le projet, vous pouvez faire ce qui suit (si vous préférez npm, toutes les commandes yarn ont des équivalents npm) :

yarn init

yarn add -D nodemon @babel/core @babel/node @babel/preset-env

Create a .babelrc file with: { “presets”: [“@babel/preset-env”] } yarn add mongoose express graphql apollo-server apollo-server-express Add the following script to your package.json: “dev”: “nodemon –exec babel-node src/index.js” Using Compass or mongo’s CLI, be sure to create a database to store your data in if you want to use a database.

Créez un fichier .babelrc avec : { “presets”: [“@babel/preset-env”] }

yarn add mongoose express graphql apollo-server apollo-server-express

Ajoutez le script suivant dans votre paquet .json : “dev”: “nodemon –exec babel-node src/index.js”

En utilisant Compass ou la ligne de commande de mongo, assurez-vous de créer une base de données pour y stocker vos données si vous voulez utiliser une base de données.

Step 1 Mongoose Schema For a mongodb implementation with mongoose, you define a mongoose.Schema (separate from the GraphQL Schema). Here you’re essentially defining the document structure to be stored/loaded from the collection(s). My Schema for Go looks like this: /src/models/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-models-gogames-js

Étape 1 - Schéma de Mongoose

Pour une implémentation de mongodb avec mongoose, vous définissez un mongoose.Schema (différent du schéma de GraphQL). Ici, vous définissez en gros la structure du document à charger/stocker depuis la (ou les) collection(s).

Mon schéma pour le Go ressemble à ceci :

/src/models/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-models-gogames-js

Basic explanation I defined fields for a ‘go’ game to include Title (i.e. Lucas VS George), the date played (currently defined as a String, as I haven’t yet figured out how to make dates work correctly), what server it was played on (KGS, IGS, FGS, online-go, etc), Black and White player names, Komi (the points given to White for going second), Result in the traditional notation - i.e. B+Res, and MyWin which tracks if I won this game (for statistics later on) - if I were to add someone else’s game, I’d simply leave this as false, and SGF. I tend to download my games’ SGF files and store them somewhere on my PC. While I won’t necessarily link them all on a web server, I can at least track the name. If I do eventually add them in as static files, I can then just update them to links.

Explication simple

J'ai défini les champs pour un jeu « go » pour inclure un Titre (par ex., Lucas vs Georges), la date du jeu (actuellement définie comme une chaîne, car je n'ai pas encore réussi à faire fonctionner correctement les dates), le serveur sur lequel le jeu a eu lieu (KGS, IGS, FGS, online-go, etc), le nom des joueurs Noir et Blanc, le Komi (les points attribués à Blanc s'il est second), le Résultat dans la notation traditionnelle - par ex., B+Res, et MyWin qui enregistre si j'ai gagné ce jeu (pour des statistiques ultérieures). Si j'avais eu à ajouter le jeu de quelqu'un d'autre, j'aurai laissé ce champ à « faux » et ajouté le SGF. J'ai tendance à télécharger les fichiers SGF de mes jeux et de les stocker quelque part sur mon PC. Alors que je ne les relie pas forcément tous à un serveur Web, je peux au moins garder une trace de leur nom. Si je veux éventuellement les ajouter comme des fichiers statiques, je peux ensuite les mettre à jour simplement avec des liens.

The collection defines what I want the collection to be called in mongodb (currently, the collection does not exist - so I could have chosen anything here). You then apply the schema to a model, and export the resulting variable to use later on. Step 2 GraphQL Schema Once we’ve defined our mongodb server, we need to define our GraphQL schema. You should base the schema off your database definition, but it does not have to be a one-to-one match.

La collection définit comment je veux l'appeler dans mongodb (actuellement, la collection n'existe pas - aussi, ici, j'aurai pu choisir n'importe quoi). Ensuite, vous appliquez le schéma à un modèle et exportez la variable résultante pour un usage ultérieur.

Étape 2 - Schéma de GraphQL

Une fois que nous avons défini notre serveur mongodb, nous devons définir notre schéma GraphQL. Vous devriez baser votre schéma sur la définition de votre base de données, mais il n'est obligatoire de les faire correspondre à 100 %.

The GraphQL Schema I defined looks like this: /src/schemas/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-schemas-gogames-js The GoGame type is a match for the mongoose Schema, and the createGoGame mutation takes pretty much all the fields.

Le schéma GraphQL que j'ai défini ressemble à ceci :

/src/schemas/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-schemas-gogames-js

Le type GoGame est une correspondance avec le schéma mongoose et la mutation createGoGame prend à peu près tous les champs.

The queries, however, are specialized. The first query (goGame) can only be filtered by ID and/or title, as it returns a single instance it makes sense to be as restrictive as possible to avoid weird results. The allGoGames query can be filtered using pretty much all fields except Komi and Result. As my goal for this API is to track my own games, I’m more likely to search for games where I was black or white, and perhaps define if it was a win or a loss. I don’t think I’ll ever search for all games where Komi was 0.5, for example. If I end up needing this, I can simply add it in as an option. Similarly, I won’t necessarily be filtering by result, as I’ll never (at that point) know which player was which. The field is important for a quick overview, but shouldn’t be very useful when filtering what I want to see. I also added a Limit field to the allGoGames, to limit the number of results returned.

Cependant, les requêtes sont spécialisées. La première requête (GoGame) ne peut être filtrée que par l'identifiant et/ou le titre ; comme elle ne retourne qu'une seule instance, c'est logique qu'elle soit aussi restrictive que possible pour éviter les résultats bizarres. La requête allGoGames peut être filtrée en utilisant à peu près tous les champs sauf Komi ou Result. Comme, pour cette API, j'ai pour objectif de garder une trace de mes propres jeux, j'ai plutôt tendance à rechercher les jeux où j'ai été Blanc ou Noir et peut-être de définir si j'ai gagné ou perdu. Je ne pense jamais chercher tous les jeux où Komi valait 0.5, par exemple. Si j'en ai finalement besoin, je pourrai simplement l'ajouter comme une option. De même, je n'aurai pas forcément besoin de filtrer suivant le résultat, car je ne saurai jamais (à cet instant-là) qui était blanc et qui, noir. Le champ est important pour un aperçu rapide, mais il n'est pas très utile quand je filtre ce que je veux voir. J'ai aussi ajouté un champ Limit à allGoGames, pour limiter le nombre des résultats retournés.

Step 3 Resolvers Okay, we’ve now defined our schemas and given some thought to the options available in a query. However, until we define our resolvers, the query won’t work. A resolver is a function that defines what happens with the parameters we defined in our schema. For my Go games, it looks like this: /src/resolvers/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-resolvers-gogames-js Admittedly, almost all my resolvers look like this, with the only difference being variable names and the models used. The goGame resolver is the simplest - I take any of the args passed through (Title or _id), and then run a findOne on the collection.

Étape 3 - Les résolveurs

Bon ! Nous avons maintenant défini nos schémas et donné quelques éléments de réflexion sur les options disponibles dans une requête. Néanmoins, jusqu'à ce que nous définissions nos résolveurs, la requête ne marchera pas. Un résolveur est une fonction qui définit ce qui arrive avec les paramètres que nous avons définis dans notre schéma. Pour les jeux de Go, il ressemble à ceci :

/src/resolvers/goGames.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-resolvers-gogames-js

J'avoue que tous mes résolveurs ressemblent à ça, à la seule différence des noms de variable et des modèles utilisés. Le résolveur GoGame est le plus simple : je prend n'importe quel argument transmis (Title ou _id) et je lance ensuite un findOne sur la collection.

The allGoGames resolver is more complicated. I pass in all the args, including a field called Limit. The idea behind ‘limit’ is to set a maximum number of results (ie. if I want a top 10). As this field doesn’t exist in the mongodb document, it will never yield results if it’s just passed in that way. Instead, I check if args has a property ‘Limit’. If it does, I create a copy of the object and delete the ‘Limit’ property. I then adjust the mongodb command to pass in the remaining arguments and use args.Limit in the .limit() function. If args.Limit doesn’t exist, I just run a find() on all the args. The createGoGame resolver takes all the arguments I specified in the GraphQL Schema. However, it also needs an id. Instead of forcing the user or client to generate one, I instead add an _id field to the object using mongoose.Types.ObjectId() before creating the item.

Le résolveur allGoGames est plus compliqué. Je passe tous les arguments, y compris un champ appelé Limit. L'idée avec « limit » est de paramétrer un nombre maximum de résultats (par ex., je veux les 10 meilleurs). Comme le champ n'existe pas dans le document mongodb, il ne sélectionnera jamais les résultats si je ne le passe que de cette façon. Au lieu de ça, je vérifie si les arguments ont une propriété « Limit ». Si oui, je crée une copie de l'objet et efface la propriété « Limit ». J'ajuste ensuite la commande de mongodb pour passer les arguments restants et utilise args.Limit dans la fonction .limit(). Si args.Limit n'existe pas, je lancerai simplement un find() sur tous les arguments.

Le résolveur de createGoGame prend tous les arguments que j'ai spécifié dans le schéma de GraphQL. Cependant, j'ai aussi besoin d'un identifiant (id). Plutôt que de forcer l'utilisateur ou le client à en générer un, j'ajoute un champ _id à l'objet en utilisant mongoose.Types.ObjectId() avant de créer l'élément.

Step 4 - Putting it all together The first thing I would recommend you do is create an index.js file in both /src/schemas and /src/resolvers. This file will serve as an aggregator for all your schemas and resolvers once you have more than one. /src/schemas/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-schemas-index-js /src/resolvers/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-resolvers-index-js

Étape 4 - Regrouper tout ça

La première chose que je vous recommande de faire est de créer un fichier index.js à la fois dans /src/schemas et dans /src/resolvers. Ce fichier servira comme agrégateur pour tous vos schémas et résolveurs dès que vous en aurez plus d'un.

/src/schemas/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-schemas-index-js

/src/resolvers/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-resolvers-index-js

Now for the heart of the server: /src/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-src-index-js Be sure to replace {MONGO_URL} with your actual mongodb connection string (most likely mongodb:localhost:27017/{database}), where {database} is whatever database name you defined manually in step 1. Maintenant, le cœur du serveur : /src/index.js: https://gist.github.com/lswest/d2118f4fa0225b80993acb7337fdefc2#file-src-index-js Assurez-vous de remplacer {MONGO_URL} par votre vraie chaîne de connexion à mongodb (en général du genre mongodb:localhost:27017/{database}), où {database} est le nom de la base de données que vous avez défini manuellement à l'étape 1. Step 5 - Trying it out Once you’ve started the server with yarn dev, the server should be running on localhost:5000. However, the root doesn’t return anything as we only defined the path “/graphql”. So head on over to http://localhost:5000/graphql and have a play around on your graphiql instance. To create items: mutation { createGoGame(Title:“Example”,PlayedDate:“2019-12-06”,Server:“Fox”,Black:“Player1”,White:“Player2”,Komi:“7.5”,Result:“B+Res”,MyWin:false,SGF:“2019-12-06 - example.sgf”) { _id } } Étape 5 - Test Une fois que vous aurez démarré le serveur avec yarn dev, le serveur devrait fonctionner à localhost:5000. Cependant, la racine ne retourne rien car nous n'avons défini que le chemin « /graphql ». Aussi, déplacez-vous vers http://localhost:5000/graphql et faites un test sur votre instance graphql. Pour créer des éléments : mutation { createGoGame(Title:“Example”,PlayedDate:“2019-12-06”,Server:“Fox”,Black:“Player1”,White:“Player2”,Komi:“7.5”,Result:“B+Res”,MyWin:false,SGF:“2019-12-06 - example.sgf”) { _id } } The above will generate an entry and return the id for you to use in a goGame query. To query items: { goGame(_id: “id from above”) { Title } } To see them all, you can also run: { allGoGames { Title } } La commande ci-dessus générera une entrée et retournera son identifiant, que vous utiliserez dans une requête goGame. Pour requêter des éléments : { goGame(_id: “id from above”) { Title } } Pour tous les voir, vous pouvez lancer aussi : { allGoGames { Title } } So, I hope this last article has gotten you enthused for GraphQL. To all my avid readers - thank you for your time and interest over these years! As always, if you want to send me a message you can reach me at lswest34+fcm@gmail.com. Especially if you happen to have a good list of my articles and what issues they appeared in! Bien ! J'espère que ce dernier article vous a intéressé à GraphQL. À tous mes fervents lecteurs : merci pour votre temps et votre intérêt durant ces années ! Comme toujours, si vous voulez m'envoyer un message, vous pouvez me joindre à lswest34+fcm@gmail.com. Particulièrement si vous avez une bonne liste de mes articles et dans quels numéros ils sont parus !

issue152/c_c.txt · Dernière modification : 2020/01/04 14:13 de auntiee