One place for hosting & domains

      GraphQL

      How To Build a GraphQL API With Golang to Upload Files to DigitalOcean Spaces


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      For many applications, one desirable feature is the user’s ability to upload a profile image. However, building this feature can be a challenge for developers new to GraphQL, which has no built-in support for file uploads.

      In this tutorial, you will learn to upload images to a third-party storage service directly from your backend application. You will build a GraphQL API that uses an S3-compatible AWS GO SDK from a Go backend application to upload images to DigitalOcean Spaces, which is a highly scalable object storage service. The Go back-end application will expose a GraphQL API and store user data in a PotsgreSQL database provided by DigitalOcean’s Managed Databases service.

      By the end of this tutorial, you will have built a GraphQL API using Golang that can receive a media file from a multipart HTTP request and upload the file to a bucket within DigitalOcean Spaces.

      Prerequisites

      To follow this tutorial, you will need:

      • A DigitalOcean account. If you do not have one, sign up for a new account. You will use DigitalOcean’s Spaces and Managed Databases in this tutorial.

      • A DigitalOcean Space with Access Key and Access Secret, which you can create by following the tutorial, How To Create A DigitalOcean Space and API Key. You can also see product documentation for How to Manage Administrative Access to Spaces.

      • Go installed on your local machine, which you can do by following our series, How to Install and Set Up a Local Programming Environment for Go. This tutorial used Go version 1.17.1.

      • Basic knowledge of Golang, which you can gain from our How To Code in Go series. The tutorial, How To Write Your First Program In Go, provides a good introduction to the Golang programming language.

      • An understanding of GraphQL, which you can find in our tutorial, An Introduction To GraphQL.

      Step 1 — Bootstrapping a Golang GraphQL API

      In this step, you will use the Gqlgen library to bootstrap the GraphQL API. Gqlgen is a Go library for building GraphQL APIs. Two important features that Gqglen provides are a schema-first approach and code generation. With a schema-first approach, you first define the data model for the API using the GraphQL Schema Definition Language (SDL). Then you generate the boilerplate code for the API from the defined schema. Using the code generation feature, you do not need to manually create the query and mutation resolvers for the API as they are automatically generated.

      To get started, execute the command below to install gqlgen:

      • go install github.com/99designs/gqlgen@latest

      Next, create a project directory named digitalocean to store the files for this project:

      Change into the digitalocean project directory:

      From your project directory, run the following command to create a go.mod file that manages the modules within the digitalocean project:

      Next, using nano or your favorite text editor, create a file named tools.go within the project directory:

      Add the following lines into the tools.go file as a tool for the project:

      // +build tools
      
       package tools
      
       import _ "github.com/99designs/gqlgen" 
      

      Next, execute the tidy command to install the gqlgen dependency introduced within the tools.go file:

      Finally, using the installed Gqlgen library, generate the boilerplate files needed for the GraphQL API:

      Running the gqlgen command above generates a server.go file for running the GraphQL server and a graph directory containing a schema.graphqls file that contains the Schema Definitions for the GraphQL API.

      In this step, you used the Gqlgen library to bootstrap the GraphQL API. Next, you’ll define the schema of the GraphQL application.

      Step 2 — Defining the GraphQL Application Schema

      In this step, you will define the schema of the GraphQL application by modifying the schema.graphqls file that was automatically generated when you ran the gqlgen init command. In this file, you will define a User, Query, and Mutation types.

      Navigate to the graph directory and open the schema.graphqls file, which defines the schema of the GraphQL application. Replace the boilerplate schema with the following code block, which defines the User type with a Query to retrieve all user data and a Mutation to insert data:

      schema.graphqls

      
      scalar Upload
      
      type User {
        id: ID!
        fullName: String!
        email: String!
        img_uri: String!
        DateCreated: String!
      }
      
      type Query {
        users: [User]!
      }
      
      input NewUser {
        fullName: String!
        email: String!
        img_uri: String
        DateCreated: String
      }
      
      input ProfileImage {
        userId: String
        file: Upload
      }
      
      type Mutation {
        createUser(input: NewUser!): User!
        uploadProfileImage(input: ProfileImage!): Boolean!
      }
      

      The code block defines two Mutation types and a single Query type for retrieving all users. A mutation is used to insert or mutate existing data in a GraphQL application, while a query is used to fetch data, similar to the GET HTTP verb in a REST API.

      The schema in the code block above used the GraphQL Schema Definition Language to define a Mutation containing the CreateUser type, which accepts the NewUser input as a parameter and returns a single user. It also contains the uploadProfileImage type, which accepts the ProfileImage and returns a boolean value to indicate the status of the success upload operation.

      Note: Gqlgen automatically defines the Upload scalar type, and it defines the properties of a file. To use it, you only need to declare it at the top of the schema file, as it was done in the code block above.

      At this point, you have defined the structure of the data model for the application. The next step is to generate the schema’s query and the mutation resolver functions using Gqlgen’s code generation feature.

      Step 3 — Generating the Application Resolvers

      In this step, you will use Gqlgen’s code generation feature to automatically generate the GraphQL resolvers based on the schema that you created in the previous step. A resolver is a function that resolves or returns a value for a GraphQL field. This value could be an object or a scalar type such as a string, number, or even a boolean.

      The Gqlgen package is based on a schema-first approach. A time-saving feature of Gqlgen is its ability to generate your application’s resolvers based on your defined schema in the schema.graphqls file. With this feature, you do not need to manually write the resolver boilerplate code, which means you can focus on implementing the defined resolvers.

      To use the code generation feature, execute the command below in the project directory to generate the GraphQL API model files and resolvers:

      A few things will happen after executing the gqlgen command. Two validation errors relating to the schema.resolvers.go file will be printed out, some new files will be generated, and your project will have a new folder structure.

      Execute the tree command to view the new files added to your project.

      tree *
      

      The current directory structure will look similar to this:

      Output

      go.mod go.sum gqlgen.yml graph ├── db.go ├── generated │   └── generated.go ├── model │   └── models_gen.go ├── resolver.go ├── schema.graphqls └── schema.resolvers.go server.go tmp ├── build-errors.log └── main tools.go 2 directories, 8 files

      Among the project files, one important file is schema.resolvers.go. It contains methods that implement the Mutation and Query types previously defined in the schema.graphqls file.

      To fix the validation errors, delete the CreateTodo and Todos methods at the bottom of the schema.resolvers.go file. Gqlgen moved the methods to the bottom of the file because the type definitions were changed in the schema.graphqls file.

      schema.resolvers.go

      
      package graph
      
      // This file will be automatically regenerated based on the schema, any resolver implementations
      // will be copied through when generating and any unknown code will be moved to the end.
      
      import (
          "context"
          "digitalocean/graph/generated"
          "digitalocean/graph/model"
          "fmt"
      )
      
      func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
          panic(fmt.Errorf("not implemented"))
      }
      
      func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
          panic(fmt.Errorf("not implemented"))
      }
      
      func (r *queryResolver) User(ctx context.Context) (*model.User, error) {
          panic(fmt.Errorf("not implemented"))
      }
      
      // Mutation returns generated.MutationResolver implementation.
      func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
      
      // Query returns generated.QueryResolver implementation.
      func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
      
      type mutationResolver struct{ *Resolver }
      type queryResolver struct{ *Resolver }
      
      // !!! WARNING !!!
      // The code below was going to be deleted when updating resolvers. It has been copied here so you have
      // one last chance to move it out of harms way if you want. There are two reasons this happens:
      //  - When renaming or deleting a resolver the old code will be put in here. You can safely delete
      //    it when you're done.
      //  - You have helper methods in this file. Move them out to keep these resolver files clean.
      
      func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
       panic(fmt.Errorf("not implemented"))
      }
      func (r *queryResolver) Todos(ctx context.Context) ([]*model.Todo, error) {
       panic(fmt.Errorf("not implemented"))
      }
      

      As defined in the schema.graphqls file, Gqlgen’s code generator created two mutations and one query resolver method. These resolvers serve the following purposes:

      • CreateUser: This mutation resolver inserts a new user record into the connected Postgres database.

      • UploadProfileImage: This mutation resolver uploads a media file received from a multipart HTTP request and uploads the file to a bucket within DigitalOcean Spaces. After the file upload, the URL of the uploaded file is inserted into the img_uri field of the previously created user.

      • Users: This query resolver queries the database for all existing users and returns them as the query result.

      Going through the methods generated from the Mutation and Query types, you would observe that they cause a panic with a not implemented error when executed. This indicates that they are still auto-generated boilerplate code. Later in this tutorial, you will return to the schema.resolver.go file to implement these generated methods.

      At this point, you generated the resolvers for this application based on the content of the schema.graphqls file. You will now use the Managed Databases service to create a database that will store the data passed to the mutation resolvers to create a user.

      Step 4 — Provisioning and Using a Managed Database Instance on DigitalOcean

      In this step, you will use the DigitalOcean console to access the Managed Databases service and create a PostgreSQL database to store data from this application. After the database has been created, you will securely store the details in a .env file.

      Although the application will not store images directly in a database, it still needs a database to insert each user‘s record. The stored record will then contain links to the uploaded files.

      A user’s record will consist of a Fullname, email, dateCreated, and an img_uri field of String data type. The img_uri field contains the URL pointing to an image file uploaded by a user through this GraphQL API and stored within a bucket on DigitalOcean Spaces.

      Using your DigitalOcean dashboard, navigate to the Databases section of the console to create a new database cluster, and select PostgreSQL from the list of databases offered. Leave all other settings at their default values and create this cluster using the button at the bottom.

      Digitalocean database cluster

      The database cluster creation process will take a few minutes before it is completed.

      After creating the cluster, follow the Getting Started steps on the database cluster page to set up the cluster for use.

      At the second step of the Getting Started guide, click the Continue, I’ll do this later text to proceed. By default, the database cluster is open to all connections.

      Note: In a production-ready scenario, the Add Trusted Sources input field at the second step should only contain trusted IP addresses, such as the IP Address of the DigitalOcean Droplet running the application. During development, you can alternatively add the IP address of your development machine to the Add Trusted Sources input field.

      Click the Allow these inbound sources button to save and proceed to the next step.

      At the next step, the connection details of the cluster are displayed. You can also find the cluster credentials by clicking the Actions dropdown, then selecting the Connection details option.

      Digitalocean database cluster credentials

      In this screenshot, the gray box at right shows the connection credentials of the created demo cluster.

      You will securely store these cluster credentials as environment variables. In the digitalocean project directory, create a .env file and add your cluster credentials in the following format, making sure to replace the highlighted placeholder content with your own credentials:

      .env

      
       DB_PASSWORD=YOUR_DB_PASSWORD
       DB_PORT=PORT
       DB_NAME=YOUR_DATABASE_NAME
       DB_ADDR=HOST
       DB_USER=USERNAME
      

      With the connection details securely stored in the .env file, the next step will be to retrieve these credentials and connect the database cluster to your project.

      Before proceeding, you will need a database driver to work with Golang’s native SQL package when connecting to the Postgres database. go-pg is a Golang library for translating ORM (object-relational mapping) queries into SQL Queries for a Postgres database. godotenv is a Golang library for loading environment credential from a .env file into your application. Lastly, go.uuid generates a UUID (universally unique identifier) for each user’s record that will be inserted into the database.

      Execute this command to install these:

      • go get github.com/go-pg/pg/v10 github.com/joho/godotenv github.com/satori/go.uuid

      Next, navigate to the graph directory and create a db.go file. You will gradually put together the code within the file to connect with the Postgres database created in the Managed Databases cluster.

      First, add the content of the code block into the db.go file. This function (createSchema) creates a user table in the Postgres database immediately after a connection to the database has been established.

      db.go

      package graph
      
      import (
          "github.com/go-pg/pg/v10"
          "github.com/go-pg/pg/v10/orm"
          "digitalocean/graph/model"
      )
      
      func createSchema(db *pg.DB) error {
          for _, models := range []interface{}{(*model.User)(nil)}{
              if err := db.Model(models).CreateTable(&orm.CreateTableOptions{
                  IfNotExists: true,
              }); err != nil {
                  panic(err)
              }
          }
      
          return nil
      }
      

      Using the IfNotExists option passed to the CreateTable method from go-pg, the createSchema function only inserts a new table into the database if the table does not exist. You can understand this process as a simplified form of seeding a newly created database. Rather than creating the Tables manually through the psql client or GUI, the createSchema function takes care of the table creation.

      Next, add the content of the code block below into the db.go file to establish a connection to the Postgres database and execute the createSchema function above when a connection has been established successfully:

      db.go

      
      import (
            // ...
      
               "fmt" 
               "os" 
          )
      
      func Connect() *pg.DB {
          DB_PASSWORD := os.Getenv("DB_PASSWORD")
          DB_PORT := os.Getenv("DB_PORT")
          DB_NAME := os.Getenv("DB_NAME")
          DB_ADDR := os.Getenv("DB_ADDR")
          DB_USER := os.Getenv("DB_USER")
      
          connStr := fmt.Sprintf(
              "postgresql://%v:%v@%v:%v/%v?sslmode=require",
              DB_USER, DB_PASSWORD, DB_ADDR, DB_PORT, DB_NAME )
      
          opt, err := pg.ParseURL(connStr); if err != nil {
            panic(err)
            }
      
          db := pg.Connect(opt)
      
          if schemaErr := createSchema(db); schemaErr != nil {
              panic(schemaErr)
          }
      
          if _, DBStatus := db.Exec("SELECT 1"); DBStatus != nil {
              panic("PostgreSQL is down")
          }
      
          return db 
      }
      

      When executed, the exported Connect function in the code block above establishes a connection to a Postgres database using go-pg. This is done through the following operations:

      • First, the database credentials you stored in the root .env file are retrieved. Then, a variable is created to store a string formatted with the retrieved credentials. This variable will be used as a connection URI when connecting with the database.

      • Next, the created connection string is parsed to see if the formatted credentials are valid. If valid, the connection string is passed into the connect method as an argument to establish a connection.

      To use the exported Connect function, you will need to add the function to the server.go file, so it will be executed when the application is started. Then the connection can be stored in the DB field within the Resolver struct.

      To use the previously created Connect function from the graph package immediately after the application is started, and to load the credentials from the .env file into the application, open the server.go file in your preferred code editor and add the lines highlighted below:

      Note: Make sure to replace the existing srv variable in the server.go file with the srv variable highlighted below.

      server.go

       package main
      
      import (
        "log"
        "net/http"
        "os"
        "digitalocean/graph"
        "digitalocean/graph/generated"
      
        "github.com/99designs/gqlgen/graphql/handler"
        "github.com/99designs/gqlgen/graphql/playground"
       "github.com/joho/godotenv"
      )
      
      const defaultPort = "8080"
      
      func main() {
           err := godotenv.Load(); if err != nil {
           log.Fatal("Error loading .env file")
          } 
      
        // ...
      
           Database := graph.Connect()
           srv := handler.NewDefaultServer(
                   generated.NewExecutableSchema(
                           generated.Config{
                               Resolvers: &graph.Resolver{
                                   DB: Database,
                               },
                           }),
               )
      
        // ...
      }
      

      In this code snippet, you loaded the credentials stored in the .env through the Load() function. You called the Connect function from the db package and also created the Resolver object with the database connection stored in the DB field. (The stored database connection will be accessed by the resolvers later in this tutorial.)

      Currently, the boilerplate Resolver struct in the resolver.go file does not contain the DB field where you stored the database connection in the code above. You will need to create the DB field.

      In the graph directory, open the resolver.go file and modify the Resolver struct to have a DB field with a go-pg pointer as its type, as shown below:

      resolver.go

      package graph
      
      import "github.com/go-pg/pg/v10"
      
      // This file will not be regenerated automatically.
      //
      // It serves as dependency injection for your app, add any dependencies you require here.
      
      type Resolver struct {
          DB *pg.DB
      }
      

      Now a database connection will be established each time the entry server.go file is run and the go-pg package can be used as an ORM to perform operations on the database from the resolver functions.

      In this step, you created a PostgreSQL database using the Managed Database service on DigitalOcean. You also created a db.go file with a Connect function to establish a connection to the PostgreSQL database when the application is started. Next, you will implement the generated resolvers to store data in the PostgreSQL database.

      Step 5 — Implementing the Generated Resolvers

      In this step, you will implement the methods in the schema.resolvers.go file, which serves as the mutation and query resolvers. The implemented mutation resolvers will create a user and upload the user’s profile image, while the query resolver will retrieve all stored user details.

      Implementing the Mutation Resolver Methods

      In the schema.graphqls file, two mutation resolvers were generated. One with the purpose of inserting the user’s record, while the other handles the profile image uploads. However, these mutations have not yet been implemented as they are boilerplate code.

      Open the schema.resolvers.go file. Modify the imports and the CreateUser mutation with the highlighted lines to insert a new row containing the user details input into the database:

      schema.resolvers.go

      package graph
      
      import (
        "context"
        "fmt"
         "time" 
      
        "digitalocean/graph/generated"
        "digitalocean/graph/model"
        "github.com/satori/go.uuid" 
      )
      
      func (r *mutationResolver) CreateUser(ctx context.Context, input model.NewUser) (*model.User, error) {
           user := model.User{ 
               ID:          fmt.Sprintf("%v", uuid.NewV4()), 
               FullName:    input.FullName, 
               Email:       input.Email, 
               ImgURI:      "https://bit.ly/3mCSn2i", 
               DateCreated: time.Now().Format("01-02-2006"), 
           } 
      
           _, err := r.DB.Model(&user).Insert(); if err != nil { 
               return nil, fmt.Errorf("error inserting user: %v", err) 
           } 
      
           return &user, nil 
      }
      
      

      In the CreateUser mutation, there are two things to note about the user rows inserted. First, each row that is inserted is given a UUID. Second, the ImgURI field in each row has a placeholder image URL as the default value. This will be the default value for all records and will be updated when a user uploads a new image.

      Next, you will test the application that has been built at this point. From the project directory, run the server.go file with the following command:

      Now, navigate to http://localhost:8080 through your web browser to access the GraphQL playground built-in to your GraphQL API. Paste the GraphQL Mutation in the code block below into the playground editor to insert a new user record.

      graphql

      
      mutation createUser {
        createUser(
          input: {
            email: "johndoe@gmail.com"
            fullName: "John Doe"
          }
        ) {
          id
        }
      }
      

      The output in the right pane will look similar to this:

      A create user mutation on the GraphQL Playround

      You executed the CreateUser mutation to create a test user with the name of John Doe, and the id of the newly inserted user record was returned as a result of the mutation.

      Note: Copy the id value returned from the executed GraphQL query. You will use the id when uploading a profile image for the test user created above.

      At this point, you have the second UploadProfileImage mutation resolver function left to implement. But before you implement this function, you need to implement the query resolver first. This is because each upload is linked to a specific user, which is why you retrieved the ID of a specific user before uploading an image.

      Implementing the Query Resolver Method

      As defined in the schema.resolvers.graphqls file, one query resolver was generated to retrieve all created users. Similar to the previous mutation resolvers methods, you also need to implement the query resolver method.

      Open scheme.resolvers.go and modify the generated Users query resolver with the highlighted lines. The new code within the Users method below will query the Postgres database for all user rows and return the result.

      schema.resolvers.go

      package graph
      
      func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
        var users []*model.User
      
        err := r.DB.Model(&users).Select()
          if err != nil {
           return nil, err
          } 
      
        return users, nil 
      }
      

      Within the Users resolver function above, fetching all records within the user table is made possible by using go-pg’s select method on the User model without passing the WHERE or LIMIT clause into the query.

      Note: For a bigger application where many records will be returned from the query, it is important to consider paginating the data returned for improved performance.

      To test this query resolver from your browser, navigate to http://localhost:8080 to access the GraphQL playground. Paste the GraphQL Query below into the playground editor to fetch all created user records.

      graphql

      
      query fetchUsers {
        users {
            fullName
            id
            img_uri
        }
      }
      

      The output in the right pane will look similar to this:

      Query result GraphQL playground

      In the returned results, you can see that a users object with an array value was returned. For now, only the previously created user was returned in the users array because that it is the only record in the table. More users will be returned in the users array if you execute the createUser mutation with new details. You can also observe that the img_uri field in the returned data has the hardcoded fallback image URL.

      At this point, you have now implemented both the CreateUser mutation and the User query. Everything is in place for you to receive images from the second UploadProfileImage resolver and upload the received image to a bucket with DigitalOcean Spaces using an S3 compatible AWS-GO SDK.

      Step 6 — Uploading Images to DigitalOcean Spaces

      In this step, you will use the powerful API within the second UploadProfileImage mutation to upload an image to your Space.

      To begin, navigate to the Spaces section of your DigitalOcean console, where you will create a new bucket for storing the uploaded files from your backend application.

      Click the Create New Space button. Leave the settings at their default values and specify a unique name for the new Space:

      Digitalocean spaces

      After a new Space has been created, navigate to the settings tab and copy the Space’s endpoint, name, and region. Add these to the .env file within the GraphQL project in this format:

      .env

      SPACE_ENDPOINT=BUCKET_ENDPOINT
      DO_SPACE_REGION=DO_SPACE_REGION
      DO_SPACE_NAME=DO_SPACE_NAME
      

      As an example, the following screenshot shows the Setting tab, and highlights the name, region, and endpoint details of the demo space (Victory-space):

      Victory-space endpoint, name, and region

      As part of the prerequisites, you created a Space Access key and Secret key for your Space. Paste in your Access and Secret keys into the .env file within the GraphQL application in the following format:

      .env

      ACCESS_KEY=YOUR_SPACE_ACCESS_KEY
      SECRET_KEY=YOUR_SPACE_SECRET_KEY
      

      At this point, you will need to use the CTRL + C key combination to stop the GraphQL server, and execute the command below to restart the GraphQL application with the new credentials loaded into the application.

      Now that your Space credentials are loaded into the application, you will create the upload logic in the UploadProfileImage mutation resolver. The first step will be to add and configure the aws-sdk-go SDK to connect to your DigitalOcean Space.

      One way to programmatically perform operations on your bucket within Spaces is through the use of compatible AWS SDKs. The AWS Go SDK is a development kit that provides a set of libraries to be used by Go developers. The libraries provided by the SDK can be used by a Go written application when performing operations with AWS resources such as file transfers to S3 buckets.

      The DigitalOcean Spaces documentation provides a list of operations you can perform on the Spaces API using an AWS SDK. We will use the aws-sdk-go SDK to connect to the your DigitalOcean Space.

      Execute the go get command to install the aws-sdk-go SDK into the application:

      • go get github.com/aws/aws-sdk-go

      Over the next few code blocks, you will gradually put together the upload logic in the UploadProfileImage mutation resolver.

      First, open the schema.resolvers.go file. Add the highlighted lines to configure the AWS SDK with the stored credentials and establish a connection with your DigitalOcean Space:

      Note: The code within the code block below is incomplete, as you are gradually putting the upload logic together. You will complete the code in the subsequent code blocks.

      schema.resolvers.go

      package graph
      
      import (
         ...
      
         "os"
      
         "github.com/aws/aws-sdk-go/aws"
         "github.com/aws/aws-sdk-go/aws/credentials"
         "github.com/aws/aws-sdk-go/aws/session"
         "github.com/aws/aws-sdk-go/service/s3"
      )
      
      func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
      
       SpaceRegion := os.Getenv("DO_SPACE_REGION")
       accessKey := os.Getenv("ACCESS_KEY")
       secretKey := os.Getenv("SECRET_KEY")
      
       s3Config := &aws.Config{
           Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""),
           Endpoint:    aws.String(os.Getenv("SPACE_ENDPOINT")),
           Region:      aws.String(SpaceRegion),
       }
      
       newSession := session.New(s3Config)
       s3Client := s3.New(newSession)
      
      }
      

      Now that the SDK is configured, the next step is to upload the file sent in the multipart HTTP request.

      One way to handle files sent is to read the content from the multipart request, temporarily save the content to a new file in memory, upload the temporary file using the aws-SDK-go library, and then delete it after an upload. Using this approach, a client application such as a web application consuming this GraphQL API still uses the same GraphQL endpoint to perform file uploads, rather than using a third party API to upload files.

      To achieve this, add the highlighted lines to the existing code within the UploadProfileImage mutation resolver in the schema.resolvers.go file:

      schema.resolvers.go

      
      package graph
      
      import (
         ...
      
         "io/ioutil"
         "bytes"
      
      )
      
      func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
      ...
      
      SpaceName := os.Getenv("DO_SPACE_NAME")
      
      ...
      
      
        userFileName := fmt.Sprintf("%v-%v", input.UserID, input.File.Filename)
        stream, readErr := ioutil.ReadAll(input.File.File)
       if readErr != nil {
           fmt.Printf("error from file %v", readErr)
       }
      
       fileErr := ioutil.WriteFile(userFileName, stream, 0644); if fileErr != nil {
           fmt.Printf("file err %v", fileErr)
       }
      
       file, openErr := os.Open(userFileName); if openErr != nil {
           fmt.Printf("Error opening file: %v", openErr)
       }
      
       defer file.Close()
      
       buffer := make([]byte, input.File.Size)
      
      _, _ = file.Read(buffer)
      
       fileBytes := bytes.NewReader(buffer)
      
       object := s3.PutObjectInput{
           Bucket: aws.String(SpaceName),
           Key:    aws.String(userFileName),
           Body:   fileBytes,
           ACL:    aws.String("public-read"),
       }
      
       if _, uploadErr := s3Client.PutObject(&object); uploadErr != nil {
           return false, fmt.Errorf("error uploading file: %v", uploadErr)
       }
      
       _ = os.Remove(userFileName)
      
      
      return true, nil
      }
      

      Using the ReadAll method from the io package in the code block above, you first read the content of the file added to the multipart request sent to the GraphQL API, and then a temporary file is created to dump this content into.

      Next, using the PutObjectInput struct, you created the structure of the file to be uploaded by specifying the Bucket, Key, ACL, and Body field to be the content of the temporarily stored file.

      Note: The Access Control List (ACL) field in the PutObjectInput struct has a public-read value to make all uploaded files available for viewing over the internet. You can remove this field if your application requires that uploaded data be kept private.

      After creating the PutObjectInput struct, the PutObject method is used to make a PUT operation, sending the values of the PutObjectInput struct to the bucket. If there is an error, a false boolean value and an error message are returned, ending the execution of the resolver function and the mutation in general.

      To test the upload mutation resolver, you can use an image of Sammy the Shark, DigitalOcean’s mascot. Use the wget command to download an image of Sammy:

      • wget https://html.sammy-codes.com/images/small-profile.jpeg

      Next, execute the cURL command below to make an HTTP request to the GraphQL API to upload Sammy’s image, which has been added to the request form body.

      Note: If you are on a Windows Operating System, it is recommended that you execute the cURL commands using the Git Bash shell due to the backslash escapes.

      • curl localhost:8080/query -F operations="{ "query": "mutation uploadProfileImage($image: Upload! $userId : String!) { uploadProfileImage(input: { file: $image userId : $userId}) }", "variables": { "image": null, "userId" : "12345" } }" -F map='{ "0": ["variables.image"] }' -F [email protected]

      Note: We are using a random userId value in the request above because the process of updating a user’s record has not yet been implemented.

      The output will look similar to this, indicating that the file upload was successful:

      Output

      {"data": { "uploadProfileImage": true }}

      In the Spaces section of the DigitalOcean console, you will find the image uploaded from your terminal:

      A bucket within Digitalocean showing a list of uploaded files

      At this point, file uploads within the application are working; however, the files are linked to the user who performed the upload. The goal of each file upload is to have the file uploaded into a storage bucket and then linked back to a user by updating the img_uri field of the user.

      Open the resolver.go file in the graph directory and add the code block below. It contains two methods: one to retrieve a user from the database by a specified field, and the other function to update the record of a user.

      resolver.go

      
      import (
      ...
      
        "digitalocean/graph/model"
        "fmt"
      )
      
      ...
      
      func (r *mutationResolver) GetUserByField(field, value string) (*model.User, error) {
          user := model.User{}
      
          err := r.DB.Model(&user).Where(fmt.Sprintf("%v = ?", field), value).First()
      
          return &user, err
      }
      
      
      func (r *mutationResolver) UpdateUser(user *model.User) (*model.User, error) {
          _, err := r.DB.Model(user).Where("id = ?", user.ID).Update()
          return user, err
      }
      
      

      The first GetUserByField function above accepts a field and value argument, both of a string type. Using go-pg’s ORM, it executes a query on the database, fetching data from the user table with a WHERE clause.

      The second UpdateUser function in the code block uses go-pg to execute an UPDATE statement to update a record in the user table. Using the where method, a WHERE clause with a condition is added to the UPDATE statement to update only the row having the same ID passed into the function.

      Now you can use the two methods in the UploadProfileImage mutation. Add the content of the highlighted code block below to the UploadProfileImage mutation within the schema.resolvers.go file. This will retrieve a specific row from the user table and update the img_uri field in the user’s record after the file has been uploaded.

      Note: Place the highlighted code at the line above the existing return statement within the UploadProfileImage mutation.

      schema.resolvers.go

      
      package graph
      
      
      func (r *mutationResolver) UploadProfileImage(ctx context.Context, input model.ProfileImage) (bool, error) {
        _ = os.Remove(userFileName)
      
       
          user, userErr := r.GetUserByField("ID", *input.UserID)
        
           if userErr != nil {
               return false, fmt.Errorf("error getting user: %v", userErr)
           }
        
         fileUrl := fmt.Sprintf("https://%v.%v.digitaloceanspaces.com/%v", SpaceName, SpaceRegion, userFileName)
        
           user.ImgURI = fileUrl
        
           if _, err := r.UpdateUser(user); err != nil {
               return false, fmt.Errorf("err updating user: %v", err)
           }
        
      
        return true, nil
      }
      

      From the new code added to the schema.resolvers.go file, an ID string and the user’s ID are passed to the GetUserByField helper function to retrieve the record of the user executing the mutation.

      A new variable is then created and given the value of a string formatted to have the link of the recently uploaded file in the format of https://BUCKET_NAME.SPACE_REGION.digitaloceanspaces.com/USER_ID-FILE_NAME. The ImgURI field in the retrieved user model was reassigned the value of the formatted string as a link to the uploaded file.

      Paste the curl command below into your terminal, and replace the highlighted USER_ID placeholder in the command with the userId of the user created through the GraphQL playground in a previous step. Make sure the userId is wrapped in quotation marks so that the terminal can encode the value properly.

      • curl localhost:8080/query -F operations="{ "query": "mutation uploadProfileImage($image: Upload! $userId : String!) { uploadProfileImage(input: { file: $image userId : $userId}) }", "variables": { "image": null, "userId" : "USER_ID" } }" -F map='{ "0": ["variables.image"] }' -F [email protected]

      The output will look similar to this:

      Output

      {"data": { "uploadProfileImage": true }}

      To further confirm that the user’s img_uri was updated, you can use the fetchUsers query from the GraphQL playground in the browser to retrieve the user’s details. If the update was successful, you will see that the default placeholder URL of https://bit.ly/3mCSn2i in the img_uri field has been updated to the value of the uploaded image.

      The output in the right pane will look similar to this:

      A query mutation to retrieve an updated user record using the GraphQL Playground

      In the returned results, the img_uri in the first user object returned from the query has a value that corresponds to a file upload to a bucket within DigitalOcean Spaces. The link in the img_uri field is made up of the bucket endpoint, the user’s ID, and lastly, the filename.

      To test the permission of the uploaded file set through the ACL option, you can open the img_uri link in your browser. Due to the default Metadata on the uploaded image, it will automatically download to your computer as an image file. You can open the file to view the image.

      Downloaded view of the uploaded file

      The image at the img_uri link will be the same image that was uploaded from the command line, indicating that the methods in the resolver.go file were executed correctly, and the entire file upload logic in the UploadProfileImage mutation works as expected.

      In this step, you uploaded an image into a DigitalOcean Space by using the AWS SDK for Go from the UploadProfileImage mutation resolver.

      Conclusion

      In this tutorial, you performed a file upload to a created bucket on a DigitalOcean Space using the AWS SDK for Golang from a mutation resolver in a GraphQL application.

      As a next step, you could deploy the application built within this tutorial. The Go Dev Guide provides a beginner-friendly guide on how to deploy a Golang application to DigitalOcean’s App Platform, which is a fully managed solution for building, deploying, and managing your applications from various programming languages.



      Source link

      An Introduction to GraphQL


      Introduction

      The author selected the Free and Open Source Fund to receive a donation as part of the Write for DOnations program.

      As web and mobile applications become more mature and complex, software engineers invent clever new ways of improving the interaction between client and server within an application. One of the biggest paradigm shifts over the last few years in this regard has been GraphQL, an open-source query language and runtime for manipulating APIs. GraphQL was designed by Facebook in 2012 (and released publicly in 2015) to solve various weaknesses with traditional REST architecture by making a new system that is declarative, client-driven, and performant.

      In this article, you will review what GraphQL is, familiarize yourself with important terminology and concepts of GraphQL, and discover how the GraphQL specification compares with the REST architectural style.

      What is GraphQL?

      GraphQL stands for Graph Query Language, but unlike other query languages such as SQL (Structured Query Language), it is not a language for communicating directly with a database, but rather a language that defines a contract through which a client communicates with a API server. The GraphQL specification is an open standard that describes the rules and characteristics of the language. It also provides instructions for executing a GraphQL query.

      Due to the fact that GraphQL is defined by an open standard, there is no official implementation of GraphQL. A GraphQL implementation can be written with any programming language, integrate with any type of database, and support any client (such as mobile or web applications), as long as it follows the rules outlined in the spec. One of the most popular commercial GraphQL implementations is Apollo GraphQL, which touts several GraphQL client and server implementations, but it is not necessary to use Apollo to use or understand GraphQL.

      GraphQL Characteristics

      There are several key characteristics of GraphQL design. GraphQL queries are declarative and hierarchical, and a GraphQL schema is strongly-typed and introspective.

      Declarative

      GraphQL queries are declarative, meaning the client will declare exactly which fields it is interested in, and the response will only include those properties.

      This example GraphQL query for a hypothetical fantasy game API requests a wizard with an ID of "1", and requests the name and race fields on that object.

      {
        wizard(id: "1") {
          name
          race
        }
      }
      

      The response, which is returned in JSON format, will return a data object that contains the found wizard object, with the two fields the query requested.

      {
        "data": {
          "wizard": {
            "name": "Merlin",
            "race": "HUMAN"
          }
        }
      }
      

      Since a GraphQL response only gives you the exact information you want, it results in a more efficient and performant network request than alternatives that always provide a complete set of data.

      Hierarchical

      GraphQL queries are also hierarchical. The data returned follows the shape of the query. In this example, the query has been extended to include spells, and is requesting the name and attack fields of every spell.

      {
        wizard(id: "1") {
          name
          spells {
            name
            attack
          }
        }
      }
      

      The response will now include an array of all the spell objects associated with this particular wizard. Although wizards and spells might be stored in separate database tables, they can be fetched with a single GraphQL request. (However, GraphQL is not opinionated about how the data itself is stored, so that is a presumption.)

      {
        "data": {
          "wizard": {
            "name": "Merlin",
            "spells": [
              {
                "name": "Lightning Bolt",
                "attack": 2
              },
              {
                "name": "Ice Storm",
                "attack": 2
              },
              {
                "name": "Fireball",
                "attack": 3
              }
            ]
          }
        }
      }
      

      Strongly-typed

      GraphQL is strongly-typed, as described by the GraphQL Type system. Types describe the capabilities of the values within a GraphQL server. The GraphQL types will be familiar to most programmers, with scalars (primitive values) like strings, booleans, and numeric integers, as well as more advanced values like objects.

      This example creates a Spell Object type with fields that correspond to String and Int scalar types.

      type Spell {
        name: String!
        attack: Int
        range: Int
      }
      

      A GraphQL schema is defined using the type system, which allows the server to determine whether or not a query is valid before attempting to query the data. GraphQL Validation ensures the request is syntactically correct, unambiguous, and mistake-free.

      Self-documenting

      The Introspection feature allows GraphQL clients and tools to query the GraphQL server for the underlying schema’s shape and data. This allows for the creation of tools like GraphiQL, an in-browser IDE and playground for working with GraphQL queries, and other tools for automatically generating documentation.

      For example, you can find out more about the Spell type through this introspection feature via the __schema.

      {
        __schema {
          types {
            name
            kind
            description
          }
        }
      }
      

      The response will also be JSON like any other GraphQL response:

      {
        "data": {
          "__schema": {
            "types": [
              {
                "name": "Spell",
                "kind": "OBJECT",
                "description": "A powerful spell that a wizard can read from a scroll."
              }
            ]
          }
        }
      }
      

      Client-driven

      The work of developing a GraphQL API happens on the backend, where the schema is defined and implemented. However, since all of the power of the GraphQL API is encompassed in a single endpoint on the server, it is up to the client via declarative queries to decide exactly what data it needs. This empowers developers to iterate quickly, as the front-end developer can continue to query the data the GraphQL API exposes without doing any additional backend work.

      Architecture

      GraphQL exists in the application layer between client and data. The GraphQL server describes the capabilities exposed in the API, and the client describes the requirements of the request.

      Server

      A GraphQL API is defined with a single endpoint, usually the /graphql endpoint, which can access the full capabilities of the GraphQL server. Since GraphQL is an application layer technology and is transport agnostic, it can be served over any protocol, but it is most commonly served over HTTP.

      A GraphQL server implementation can be written with any programming language, such as the express-graphql middleware which allows you to create a GraphQL API on a Node/Express HTTP server. GraphQL is also database agnostic, and the data for the application can be stored in MySQL, PostgreSQL, MongoDB, or any other database. The data can even be supplied by an aggregation of several traditional REST API endpoints. All that matters is that the data is defined in a GraphQL schema, which defines the API by describing the data available to be queried.

      Client

      Requests made to a GraphQL server are called documents and consist of operations such as queries (for read requests) and mutations (for write requests).

      Although there are advanced GraphQL clients, such as Apollo Client or Facebook’s Relay which provide mechanisms for caching as well as additional tools, no special client is required to make a request to a GraphQL server. A simple XMLHttpRequest or fetch from a web browser is sufficient for making requests by sending a GraphQL document to a GraphQL server.

      Below is an example of a fetch request to a /graphql endpoint, which passes the GraphQL document as a string in the body of the POST request.

      async function fetchWizards() {
        const response = await fetch('/graphql', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            query: `{
          wizards {
            id
            name
          },
        }`,
          }),
        })
        const wizards = await response.json()
      
        return wizards
      }
      
      fetchWizards()
      

      This will return a JSON response for the request.

      {
        "data": {
          "wizards": [
            { "id": "1", "name": "Merlin" },
            { "id": "2", "name": "Gandalf" }
          ]
        }
      }
      

      GraphQL vs. REST

      GraphQL and REST are not interchangeable concepts, but they solve similar problems for applications. REST stands for Representational State Transfer, and is a software architectural style for sharing data between different systems. A RESTful API is an API that adheres to the principles and constraints of REST, which includes being stateless, cacheable, enforcing a separation of concerns between the client and server, and having a uniform interface, such as through URIs. GraphQL, as covered previously, is a specification for a query language and runtime for executing queries.

      There are advantages and disadvantages to both systems, and both have their use in modern API development. However, GraphQL was developed to combat some perceived weaknesses with the REST system, and to create a more efficient, client-driven API.

      • Architecture – A REST API is typically defined by multiple endpoints on a server, but GraphQL exchanges data over a single endpoint. A GraphQL endpoint can return a complex graph of data that might require multiple REST queries, reducing the number of requests over the network for a single view.

      • Data fetching – A REST API returns the set of data that was determined on the server. This might be far too much data, such as if the view only requires one property from a response. It also might not be enough, such as a list endpoint that doesn’t return every property that a table requires in the view. GraphQL prevents this over and under fetching of data via declarative queries.

      • Error Handling – Since it is not necessary for GraphQL to be served over HTTP, there is no specification about using HTTP response codes for errors. Typically all GraphQL endpoints will resolve with a 200 HTTP code response, and failed results will include an errors property alongside the data property in the response. RESTful APIs, on the other hand, utilize different 400 level HTTP codes for client errors and 200 level HTTP codes for successful responses.

      • Versioning – GraphQL APIs strive to be backwards compatible and avoid breaking changes, contrasting with the common REST pattern of versioning endpoints, often with a /v1 or /v2 in the URL itself to determine the version. However, it is possible to implement your own versioning with GraphQL, or version via evolution with REST, it’s just less conventional.

      • Caching – Cacheability is an integral part of the REST guiding constraints. Since HTTP-based REST APIs consist of multiple endpoints using different HTTP methods, it can take advantage of existing HTTP conventions for caching and avoiding refetching resource. Since essentially every GraphQL request will be different but use the single endpoint, it cannot take advantage of any of the built-in HTTP caching mechanisms. GraphQL clients can take advantage of Global Object Identification to enable simple caching.

      This list does not cover all the similarities and differences between REST and GraphQL, but summarizes many of the most critical points. Additionally, GraphQL can be used as a gateway that aggregates multiple REST endpoints or services, in which case both technologies can be used in harmony side-by-side.

      FeatureGraphQLREST
      DescriptionGraphQL is a query language for APIs, and a server-side runtimeAn architectural style for designing web services
      Data FetchingA single HTTP endpoint that responds to deterministic queriesA set of HTTP endpoints that typically return a predetermined dataset
      VersioningVersioning discouragedVersioning common
      HTTP Status CodesAll responses, including errors, are typically 200Implements HTTP Status codes
      ValidationBuilt-in metadata validationValidation must be manually implemented
      DocumentationBuilt-in via type system and introspectionNot self-documenting, tools like OpenAPI available
      CachingNoYes
      Request MethodsQueries, mutations, and subscriptions (over POST for HTTP)All HTTP methods utilized (GET, POST, PATCH, PUT, DELETE, etc)
      Response Content-TypeJSONAny (JSON, XML, HTML, etc.)

      Conclusion

      GraphQL is an open-source query language and runtime for APIs. GraphQL was invented by developers at Facebook to solve various issues encountered with traditional REST APIs, such as over/under fetching data and inefficient network requests, by making a client-driven, declarative query language for APIs.

      While GraphQL is not an interchangeable concept with REST, they both describe different ways to manage communication between a client and a server. In this article, you learned what GraphQL is, key differences and similarities between GraphQL and REST, and how a GraphQL server exposes data to a client.



      Source link

      How To Handle Images with GraphQL and the Gatsby Image API


      The author selected /dev/color to receive a donation as part of the Write for DOnations program.

      Introduction

      Handling images plays a pivotal role in building websites, but also can be challenging to deal with. Unoptimized images slow down websites, and many images that might look appropriate on a desktop are hard to scale down to a mobile device. Visually manipulating an image can also be tedious and difficult to maintain.

      All of these problems in isolation are not a big issue. The main problem is when you have to keep track of all of these rules and image-scaling techniques. When it comes to Gatsby.js projects, this is where the Gatsby Image API comes in handy. By using GraphQL queries, you can use the Gatsby Image API to take care of image compression, make an image responsive, and even handle basic image styling.

      In this tutorial, you are going to compress, transform, and style images using the Gatsby Image API and GraphQL queries.

      Prerequisites

      Step 1 — Setting Up a New Gatsby Project

      In this first step, you are going to set up a new Gatsby project and familiarize yourself with the key image plugins that you’ll use throughout this tutorial. You will also download and set up an image to optimize throughout the tutorial.

      First, use the CLI tool to start a new project named gatsby-image-project:

      • gatsby new gatsby-image-project https://github.com/gatsbyjs/gatsby-starter-default

      This creates a new website from the starter template in the [gatsby-starter-default](https://github.com/gatsbyjs/gatsby-starter-default) GitHub repository from Gatsby.

      Once the project is created, move into the new project directory:

      Next, open up the index.js file in a text editor of your choice:

      Delete all of the code between the layout wrapper component so that your file is the same as the following:

      gatsby-image-project/src/pages/index.js

      import React from "react"
      import { Link } from "gatsby"
      
      import Layout from "../components/layout"
      import Image from "../components/image"
      import SEO from "../components/seo"
      
      const IndexPage = () => (
        <Layout>
        </Layout>
      )
      
      export default IndexPage
      

      Next, replace the deleted code with the following highlighted JSX, which adds some HTML elements to the website:

      gatsby-image-project/src/pages/index.js

      import React from "react"
      import { Link } from "gatsby"
      
      import Layout from "../components/layout"
      import Image from "../components/image"
      import SEO from "../components/seo"
      
      const IndexPage = () => (
          <Layout>
            <div className="layout">
              <Image className="left-image"/>
              <h2>Hello</h2>
              <p>Welcome to my humble site</p>
              <p>All of our shirts are on sale!</p>
              <button className="shop-button">Shop</button>
            </div>
          </Layout>
      )
      
      export default IndexPage
      

      The Gatsby Image API will provide your new test image to the Image element in a later step. With this, you now have HTML to experiment with.

      Later in the tutorial, you’ll revisit the index.js page. For now, save and exit the file.

      The next file to open is gatsby-config.js. In this file, you will find the plugins responsible for processing images.

      Open up the file with the following:

      Once you have opened the gatsby-config file, locate the gatsby-plugin-sharp, gatsby-transformer-sharp, and gatsby-source-filesystem plugins:

      gatsby-image-project/gatsby-config.js

      module.exports = {
        siteMetadata: {
          title: `Gatsby Default Starter`,
          description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
          author: `@gatsbyjs`,
        },
        plugins: [
          `gatsby-plugin-react-helmet`,
          {
            resolve: `gatsby-source-filesystem`,
            options: {
              name: `images`,
              path: `${__dirname}/src/images`,
            },
          },
          `gatsby-transformer-sharp`,
          `gatsby-plugin-sharp`,
          {
            resolve: `gatsby-plugin-manifest`,
            options: {
              name: `gatsby-starter-default`,
              short_name: `starter`,
              start_url: `/`,
              background_color: `#663399`,
              theme_color: `#663399`,
              display: `minimal-ui`,
              icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
            },
          },
          // this (optional) plugin enables Progressive Web App + Offline functionality
          // To learn more, visit: https://gatsby.dev/offline
          // `gatsby-plugin-offline`,
        ],
      }
      

      These plugins are as follows:

      • gatsby-plugin-sharp: Sharp is an image optimization library that Gatsby uses to process images. The gatsby-plugin-sharp provides a bridge between Sharp and Gatsby.

      • gatsby-transformer-sharp: This plugin performs image transformations, such as resizing, compressing, and changing background color.

      • gatsby-source-filesystem: This plugin allows you to source data from your filesystem into your application. In this case, it enables GraphQL to query images.

      Now that you have an idea of which plugins are used to process images, close the file.

      Next, add an image to your application to optimize, edit, and style later. In this tutorial, you are going to download an image from the Unsplash stock image website. Navigate to this picture of clothes on Unsplash in a browser and download the angela-bailey-jlo7Bf4tUoY-unsplash.jpg image into the /images folder of your Gatsby project. The image must be located in the right directory in order to query the image with GraphQL and transform it using Gatsby’s Image API.

      Alternatively, you can download the image from the command line. First, move to the images directory:

      Next, execute the following command:

      • curl -sL https://images.unsplash.com/photo-1556905055-8f358a7a47b2 -o angela-bailey-jlo7Bf4tUoY-unsplash.jpg

      This will use curl to download the image and output the file as angela-bailey-jlo7Bf4tUoY-unsplash.jpg.

      In this section, you set up your Gatsby project to use Gatsby’s Image API. You explored the Gatsby configuration file to find gatsby-plugin-sharp, gatsby-transform-sharp, and gatsby-source-filesystem, which work together to optimize images. In the next step, you will test out querying and optimizing your image using GraphiQL, the GraphQL integrated development environment (IDE).

      Step 2 — Querying Images with GraphQL

      You are now going to query your new image using GraphQL. GraphQL is a query language for obtaining information from an API. It is also the data layer of Gatsby.

      First, return to the root of your Gatsby project, then start the development server:

      After your site finishes building, you will receive the following output:

      Output

      ... success open and validate gatsby-configs - 0.081s success load plugins - 4.537s success onPreInit - 0.070s success initialize cache - 0.034s success copy gatsby files - 0.320s success onPreBootstrap - 0.177s success createSchemaCustomization - 0.050s success Checking for changed pages - 0.003s success source and transform nodes - 0.264s success building schema - 0.599s info Total nodes: 35, SitePage nodes: 1 (use --verbose for breakdown) success createPages - 0.057s success Checking for changed pages - 0.005s success createPagesStatefully - 0.188s success update schema - 0.046s success write out redirect data - 0.006s success Build manifest and related icons - 0.456s success onPostBootstrap - 0.465s info bootstrap finished - 19.515s success onPreExtractQueries - 0.003s success extract queries from components - 0.932s success write out requires - 0.029s success run page queries - 0.039s - 1/1 25.97/s ⠀ You can now view gatsby-starter-default in the browser. ⠀ http://localhost:8000/ ⠀ View GraphiQL, an in-browser IDE, to explore your site's data and schema ⠀ http://localhost:8000/___graphql ⠀ Note that the development build is not optimized. To create a production build, use gatsby build ⠀ warn ESLintError: /your_filepath/gatsby-image-project/src/pages/index.js 2:10 warning 'Link' is defined but never used no-unused-vars 6:8 warning 'SEO' is defined but never used no-unused-vars ✖ 2 problems (0 errors, 2 warnings) success Building development bundle - 10.814s

      This output contains two links. The first link, https://localhost:8000/, is where you can find your local development site. The second link, http://localhost:8000/___graphql, is the location of GraphiQL. GraphiQL is an integrated development editor (IDE) that allows you to make queries in the browser. This is a useful tool that helps you experiment and make data queries before you add them to your codebase. GraphiQL only works when you are running the development server.

      With the help of GraphiQL, you can try out queries to retrieve your newly downloaded image. Open your browser and enter the GraphiQL URL http://localhost:8000/___graphql into the address bar. The browser will display the GraphiQL interface:

      Screenshot of the GraphQL IDE

      GraphiQL is split into three sections. To the far left is the Explorer, where you can find the fields that you are able to access via GraphQL. In the middle of the IDE is the sandbox where you make queries. Finally, to the far right you can find GraphQL’s documentation.

      Your first goal is to query the angela-bailey-jlo7Bf4tUoY-unsplash.jpg image. Since the image is located in the local filesystem, you choose file in the Explorer box. This will show a dropdown menu of subdirectories. You will search for the image file by relative path, so select on relativePath. relativePath reveals another set of subfolders. You will enter the exact path of the image, so choose the eq for “equals”. Inside the quotes enter the path of the image angela-bailey-jlo7Bf4tUoY-unsplash.jpg.

      GraphQL IDE showing the query to locate the image in the filesystem

      Now you are set to try out your first image manipulation. Your original image is 1920 by 1280 pixels. That is too big for your landing page, and it would help to make the image responsive. Normally, you would have to hard code the width into CSS and add media queries to make the image responsive. Gatsby’s Image API does all of that work for you, without you needing to write extra CSS.

      Select childImageSharp in the Explorer box, which transforms the image under the hood. Make sure to choose this from the top-level menu, not from under file. Choose fluid from the next dropdown. A fluid image stretches to fill its container. In the dropdown options for fluid, check maxWidth and enter the 750.

      The blue values just below the purple querying parameters are the different values you can return. Choose src and srcSet to return the location of the original and transformed image. Then click the play button to view the results:

      childImageSharp parameters

      After selecting the parameters, GraphiQL builds the following query:

      query MyQuery {
        file(relativePath: {eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg"}) {
          childImageSharp {
            fluid(maxWidth: 750) {
              src
              srcSet
            }
          }
        }
      }
      

      In the box on the right of the GraphiQL interface, you will find the return values. This will show:

      • src: Location of the image after processing.

      • srcSet: Same image set to a different size. This feature comes in handy if you want your images to be responsive.

      You do not have to choose fluid in your query; you also have the option to choose fix. A fixed image creates responsive images 1x, 1.5x, and 2x pixel densities using the <picture> element. The following is an example of a fixed query:

      query MyQuery {
        file(relativePath: {eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg"}) {
          childImageSharp {
            fixed(cropFocus: CENTER) {
              src
              srcSet
            }
          }
        }
      }
      

      This query will return the following:

      {
        "data": {
          "file": {
            "childImageSharp": {
              "fixed": {
                "src": "/static/8e3a47b77ddf6636755d7be661d7b019/0ad16/angela-bailey-jlo7Bf4tUoY-unsplash.jpg",
                "srcSet": "/static/8e3a47b77ddf6636755d7be661d7b019/0ad16/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 1x,n/static/8e3a47b77ddf6636755d7be661d7b019/44157/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 1.5x,n/static/8e3a47b77ddf6636755d7be661d7b019/7fddd/angela-bailey-jlo7Bf4tUoY-unsplash.jpg 2x"
              }
            }
          }
        },
        "extensions": {}
      }
      

      In this query you have a fixed version of the image, with the crop focus set to center. Your return value is the location of the image and a set of different image sizes (1x, 1.5x, 2x respectively).

      Now that you have tested out the GraphQL query using GraphiQL and the childImageSharp node, you will next add the queried image to a template and further optimize it using Gatsby’s Image API.

      Step 3 — Optimizing Your Image’s Performance for the Web

      In this section you are going to transfer your GraphQL image to the index.js page of your project and perform more image optimizations.

      From your terminal in the root of your Gatsby project, open the image component in your favorite text editor:

      • nano src/components/image.js

      Now, use the query that you tried out in the GraphiQL interface. Delete "gatsby-astronaut.png" and replace it with "angela-bailey-jlo7Bf4tUoY-unsplash.jpg". Also replace maxWidth: 300 with maxWidth: 750:

      gatsby-image-project/src/components/image.js

      import React from "react"
      import { useStaticQuery, graphql } from "gatsby"
      import Img from "gatsby-image"
      
      /*
       * This component is built using `gatsby-image` to automatically serve optimized
       * images with lazy loading and reduced file sizes. The image is loaded using a
       * `useStaticQuery`, which allows us to load the image from directly within this
       * component, rather than having to pass the image data down from pages.
       *
       * For more information, see the docs:
       * - `gatsby-image`: https://gatsby.dev/gatsby-image
       * - `useStaticQuery`: https://www.gatsbyjs.com/docs/use-static-query/
       */
      
      const Image = () => {
        const data = useStaticQuery(graphql`
          query {
            placeholderImage: file(relativePath: { eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg" }) {
              childImageSharp {
                fluid(maxWidth: 750) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        `)
      
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
      }
      
      export default Image
      

      ...GatsbyImageSharpFluid is a GraphQL fragment. This syntax allows you to obtain all of the different return values for childImageSharp and fluid. You will use this as the return value instead of src and srcSet.

      GraphiQL explorer list of return values for `childImageSharp` and `fluid`

      In the image.js file, after the GraphQL query there is an if statement:

      gatsby-image-project/src/components/image.js

      ...
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} />
      }
      
      export default Image
      

      placeholderimage is the alias that is given in the query and returned in <Img fluid={data.placeholderImage.childImageSharp.fluid} />. In GraphQL you are able to name your queries. In the conditional statement, if you don’t have an image, then the words Picture not found will appear on the landing page.

      Save and close this file.

      Once you build your Gatsby site, the image will be processed and optimized by the Gatsby Image API. This will decrease the size of the image file to decrease loading time for your site. To test this out, navigate to the images directory to find the original file size:

      Now list out the files with the following command:

      The -sh flag will show you the memory size of the files in a human-readable format. You will receive the following output:

      Output

      total 5.0M 4.8M angela-bailey-jlo7Bf4tUoY-unsplash.jpg 164K gatsby-astronaut.png 24K gatsby-icon.png

      Without any optimization, the image is 4.8M. Now you will look at how big the image is after you’ve used Gatsby’s Image API.

      Navigate to the root of your project and start the development server:

      Once the development server has started, place the local address into the browser. Once the site has loaded, right-click the image and select Inspect.

      Image with Inspect dropdown menu

      Now navigate to the Network tab of your browser’s developer tools. This tutorial will use Google Chrome DevTools:

      Local image size

      Your image went from 4.8 MB to 68.4 kB. That is significantly smaller than if you hadn’t used the Gatsby Image API.

      Note: If the HTTP status for the image request is 304, the image may be significantly smaller due to caching. To get a more reliable view of the image size, clear the cache and refresh the page.

      Keep developer tools open and head over to the Elements tab. Hover over the image. You will find the HTML element <picture>...</picture> and its child <source>...</source>:

      Rendered HTML of the Gatsby site

      In the source tag, you can find the srcSet attribute (the same one you queried for in GraphQL). You will also find that the different heights and widths of the image were automatically generated. These different images ensure that angela-bailey-jlo7Bf4tUoY-unsplash.jpg is fluid, without needing to change CSS.

      Note: While hovering over the image you might have noticed that the image is not keyboard-focusable, which could be an accessibility problem. If you want to learn more about accessibility, you can check out the Ally Project checklist.

      In this section you used the GraphQL Image query in your Gatsby template. You also optimized angela-bailey-jlo7Bf4tUoY-unsplash.jpg without having to write extra CSS.

      In the next section, you will visually transform the image by using childSharpImage.

      Step 4 — Styling Your Image with childSharpImage

      In this section, you will transform the look of angela-bailey-jlo7Bf4tUoY-unsplash.jpg with the help of childSharpImage. To test this, you will change your image to grayscale.

      Open image.js in a text editor:

      • nano src/components/image.js

      Go into your placeholderImage GraphQL query and inside of fluid set grayscale to true:

      gatsby-image-project/src/components/image.js

      import React from "react"
      import { useStaticQuery, graphql } from "gatsby"
      import Img from "gatsby-image"
      
      /*
       * This component is built using `gatsby-image` to automatically serve optimized
       * images with lazy loading and reduced file sizes. The image is loaded using a
       * `useStaticQuery`, which allows us to load the image from directly within this
       * component, rather than having to pass the image data down from pages.
       *
       * For more information, see the docs:
       * - `gatsby-image`: https://gatsby.dev/gatsby-image
       * - `useStaticQuery`: https://www.gatsbyjs.com/docs/use-static-query/
       */
      
      const Image = () => {
        const data = useStaticQuery(graphql`
          query {
            placeholderImage: file(relativePath: { eq: "angela-bailey-jlo7Bf4tUoY-unsplash.jpg" }) {
              childImageSharp {
                fluid(
                  maxWidth: 750
                  grayscale: true
                  ) {
                  ...GatsbyImageSharpFluid
                }
              }
            }
          }
        `)
      
        if (!data?.placeholderImage?.childImageSharp?.fluid) {
          return <div>Picture not found</div>
        }
      
        return <Img fluid={data.placeholderImage.childImageSharp.fluid} tabIndex='0' />
      }
      
      export default Image
      

      Save and close the file.

      Go back to your terminal and restart the server. You will find your image changed using childImageSharp, without writing and maintaining unnecessary CSS:

      Grayscale image

      Grayscale is just one of the many ways you can process your image using childImageSharp. Go to Gatsby’s documentation if you are curious about the other childImageSharp props.

      In this section you implemented grayscale by using childImageSharp. With this knowledge, you can perform many more image manipulations by leveraging the Gatsby Image API.

      Conclusion

      In this tutorial, you set your Gatsby project up to use the Gatsby Image API, query your Image using GraphQL, optimize your image’s performance, and style your image using childImageSharp. If you would like to learn more about Gatsby, check out the official Gatsby documentation.



      Source link