Simple GraphQL API in AWS Lambda

Simple GraphQL API in AWS Lambda

I am building a simple app. The internal APIs which the app uses are all GraphQL endpoints. What's remaining is an API to look up location info of an IP address. I've found this free API at https://ip-api.com/docs, but it doesn't have a GraphQL endpoint. I don't want my app to support heterogeneous API call stacks, nor do I want to include additional libraries because of different API stacks.

Continuing my journey of taking advantage of free cloud computing resources, I am going to build a GraphQL server adapter and deploy it in AWS Lambda. Lambda offers one million function calls for free each month!

Dependencies

I'll use Node. Dependencies are:

  "dependencies": {
    "aws-serverless-express": "^3.3.8",
    "axios": "^0.19.2",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "express-graphql": "^0.9.0",
    "graphql": "^15.1.0"
  }
  

Typical express server, with graphql module and hooked using express-graphql. axios is used to make outbound API calls. cors is there because my web app will be hosted on a different domain than AWS Lambda. aws-serverless-express proxies an express server request and response to Lambda.

Schema

Since this is a simple adapter, I'll use the original ip-api.com JSON response fields as-is. I'll create a lookup query which takes one argument called query (the IP address to be looked up). As an adapter, the resolver for the query simply calls the real ip-api.com API and graphql module formats the response.

// schema.js
const axios = require('axios');
const { GraphQLObjectType, GraphQLInt, GraphQLString,  GraphQLBoolean, GraphQLList, GraphQLSchema } = require('graphql');

const IPGeoType = new GraphQLObjectType({
    name: 'IPGeo',
    fields: () => ({
        continent: { type: GraphQLString },
        continentCode: { type: GraphQLString },
        country: { type: GraphQLString },
        countryCode: { type: GraphQLString},
        region: { type: GraphQLString },
        regionName: { type: GraphQLString },
        city: { type: GraphQLString },
        district: { type: GraphQLString },
        zip: { type: GraphQLString },
        lat: { type: GraphQLString },
        lon: { type: GraphQLString },
        timezone: { type: GraphQLString },
        offset: { type: GraphQLInt },
        currency: { type: GraphQLString },
        isp: { type: GraphQLString },
        org: { type: GraphQLString },
        as: { type: GraphQLString },
        asname: { type: GraphQLString },
        mobile: { type: GraphQLBoolean },
        proxy: { type: GraphQLBoolean },
        hosting: { type: GraphQLBoolean },
        query: { type: GraphQLString }
    })
});

// Root query
const RootQuery = new GraphQLObjectType({
    name: 'RootQueryType',
    fields: {
        lookup: {
            type: IPGeoType,
            args: {
                query: { type: GraphQLString }
            },
            resolve(parent, args) {
                return axios.get(`https://ip-api.com/json/${args.query}?fields=status,message,continent,continentCode,country,countryCode,region,regionName,city,district,zip,lat,lon,timezone,offset,currency,isp,org,as,asname,mobile,proxy,hosting,query`)
                .then(res => res.data);
            }
        }
    }
});

module.exports = new GraphQLSchema({
    query: RootQuery
});

App

The main entry point (app.js) is simply an express server. The handler is express-graphql's graphHTTP. I pass in my schema above, and enable graphiql (a web tool to compose and make GraphQL calls) for convenience of debugging.

// app.js
const express = require('express');
const graphqlHTTP = require('express-graphql');
const cors = require('cors');
const schema = require('./schema.js');

const app = express();
app.use(cors());
app.use('/', graphqlHTTP({
    schema: schema,
    graphiql: true
}));


module.exports = app;

Local and Lambda Deployments

In order to test before going to Lambda, I have server.js to start a dev server locally. For Lambda deployment, it will use index.js (as we will see later).

//server.js for local
const app = require('./app');
const PORT = process.env.PORT || 5000;

app.listen(PORT, () => console.log(`server started on port ${PORT}`));

and

// index.js for Lambda
const awsServerlessExpress = require('aws-serverless-express');
const app = require('./app');
const server = awsServerlessExpress.createServer(app);


exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context);

That's all the coding! The complete source code is on github.

I can run "npm install && npm run server" to bring up dev server locally, and hit https://localhost:5000 to test.

Lambda

No alt text provided for this image

First, I'll create a new Lambda function. From AWS Lambda service home screen, click on Create function button, then choose "Author from scratch" option.

Give it any name, eg., "ipgeo".

Leave the default Runtime to the latest version of Node.js.

Under permissions, "create a new role with basic Lambda permissions".

Click "Create function".

By default, Lambda lets you author code in browser under Function code section. Because I have dependency modules, I'll need to upload a zip of my local code. Note that I need to "npm install" the node_modules before I zip up my files. The zip root should be the folder that contains index.js.

Drop down Actions button beside "Fucntion code" and choose to "Upload a .zip file".

No alt text provided for this image

After uploading the zip, the code authoring section looks something like this:

No alt text provided for this image

AWS API Gateway

In order to expose the API, I make use of AWS API Gateway. Okay, I lied. API Gateway isn't free. It cost $1.00 for 1 million HTTP API calls. I can't find out how to do it completely free in AWS. If you know, please help me save $1.00.

Scroll up on the Lambda page, find the Add Trigger button, add API Gateway as trigger.

No alt text provided for this image

Choose "Create an API", and "HTTP API", Change Security to "Open", click "Add".

No alt text provided for this image

After an API Gateway is successfully added, I am brought back to the Lambda page. API Gateway is shown hooked up with ipgeo Lambda function.

Expand API Gateway Details, I can get the API endpoint URL.

No alt text provided for this image

Click on the URL, GraphiQL interface shows up like this:

No alt text provided for this image

Yes!! It's successfully deployed! Let's test it out.

Testing the Lambda API

On the left hand side, enter a lookup query for an IP address:

{
  lookup(query: "213.12.222.22") {
    continent
    continentCode
    country
    countryCode
    region
    regionName
    city
    district
    zip
    lat
    lon
    timezone
    offset
    currency
    isp
    org
    as
    asname
    mobile
    proxy
    hosting
    query
  }
}

Ctrl-Enter to run the query, you should get the IP location info back.

{
  "data": {
    "lookup": {
      "continent": "Europe",
      "continentCode": "EU",
      "country": "United Kingdom",
      "countryCode": "GB",
      "region": "ENG",
      "regionName": "England",
      "city": "London",
      "district": "",
      "zip": "W14 0UF",
      "lat": "51.4942",
      "lon": "-0.214694",
      "timezone": "Europe/London",
      "offset": 3600,
      "currency": "GBP",
      "isp": "EDEX",
      "org": "Cable & Wireless UK P.U.C.",
      "as": "",
      "asname": "",
      "mobile": false,
      "proxy": false,
      "hosting": false,
      "query": "213.12.222.22"
    }
  }
}

It works!

No alt text provided for this image

The beauty of GraphQL is that its schema is exposed to the endpoint, so GraphiQL is aware of the schema and and prompt for the fields. Use Ctrl-space for the tool to suggest what fields to use.

And, the client can choose which fields the API responds with. For example, if the client is only interested in lat and lon of an IP address, the query can specify:

{ 
  lookup (query: "123.123.123.123") {
		lat
    lon
  }
}

That's it for now. Have fun!



Kalvin Wei

Cloud Support Engineer / Web Developer

3 年

This article helped me a lot to understand what is what and how they work with each other. Many thanks to you, Kenny! ??

回复
Ricardo Sueiras

Helping developers experience the future now

4 年

Nice walk through Kenny.

回复

要查看或添加评论,请登录

Kenny Shi的更多文章

  • Free Web Hosting on IBM Cloud

    Free Web Hosting on IBM Cloud

    In this age, all cloud computing companies offer some kind of free services. We are not talking about limited-time free…

    1 条评论
  • Decoding EMV Contactless

    Decoding EMV Contactless

    Last time we used Smart Card Shell to look into how EMV Chip/Contact communication works. This time, we will try to…

    4 条评论
  • Integration Comparison: Adyen, Braintree, Stripe

    Integration Comparison: Adyen, Braintree, Stripe

    When a new online merchant chooses a payment gateway, one likely narrows them down to Adyen, Braintree, PayPal, Stripe.…

  • PCI-DSS For Online Merchants

    PCI-DSS For Online Merchants

    PCI-DSS (Payment Card Industry - Data Security Standard) was invented because of the data breaches. Data breach is one…

  • Thoughts on Amazon Fraud Detector

    Thoughts on Amazon Fraud Detector

    Over the past 4 years, I have been exclusively operating on AWS as my cloud computing platform. AWS offers so many…

  • Examining Your EMV Chip Cards

    Examining Your EMV Chip Cards

    Now we are a few years into EMV mandates in the US, we all have one or more EMV chip cards in our wallet and have used…

  • Overloaded "Online vs Offline" in EMV Card Processing

    Overloaded "Online vs Offline" in EMV Card Processing

    When EMV card processing is discussed, one confusing usage of terminology is Online vs Offline. They mean different…

  • Linux or Windows?

    Linux or Windows?

    I grew up with command line interfaces. My first computer was a Lambda PC-8300, which only had BASIC in ROM; then DOS…

    2 条评论
  • Hiring and Firing

    Hiring and Firing

    For the past week, I happened to be exposed to events, conversations, advise about career, interview and hiring, so I…

    1 条评论
  • Working With Fraud and Risk Vendors

    Working With Fraud and Risk Vendors

    In a recent Trust and Safety Meetup, we had a good discussion around "build or buy". Today's fraud and risk solution…

    3 条评论

社区洞察

其他会员也浏览了