├── .editorconfig ├── .eslintignore ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── lib ├── __tests__ │ ├── geocodioLibraryNode.test.js │ ├── geocodioLibraryNodeErrorHandling.test.js │ └── stubs │ │ └── sample_list.csv ├── index.d.ts └── index.js ├── package-lock.json └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Geocodio CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node-version: [18.x, 20.x, 22.x, 23.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | cache: "npm" 24 | 25 | - name: Install dependencies 26 | run: npm ci 27 | 28 | - name: Run tests (includes eslint via pretest) 29 | run: npm test 30 | env: 31 | GEOCODIO_API_KEY: ${{ secrets.GEOCODIO_API_KEY }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v12 4 | - v10 5 | - v8 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `geocodio-library-node` will be documented in this file 4 | 5 | ## 1.11.0 - 2025-05-21 6 | 7 | - Enhanced TypeScript type definitions 8 | - Fixed input type constraints to match expected properties 9 | 10 | ## 1.10.0 - 2025-05-20 11 | 12 | - Added support for Geocodio API v1.8 13 | 14 | ## 1.9.1 - 2025-03-06 15 | 16 | - Updated typescript definitions thanks to contribution by [Adrastopoulos](https://github.com/Geocodio/geocodio-library-node/pull/43) 17 | - Bumped `jest` from 26.6.3 to 29.7.0 18 | 19 | ## 1.8.0 - 2025-03-06 20 | 21 | - feat(ci): Add GH Actions by @sixlive in https://github.com/Geocodio/geocodio-library-node/pull/41 22 | - feat(auth): Use Authorization header for auth by @sixlive in https://github.com/Geocodio/geocodio-library-node/pull/40 23 | - chore(deps): Bump axios from 1.6.7 to 1.7.4 by @dependabot in https://github.com/Geocodio/geocodio-library-node/pull/42 24 | 25 | ## 1.7.0 - 2025-02-25 26 | 27 | - Added typescript definitions thanks to contribution by [Adrastopoulos](https://github.com/Geocodio/geocodio-library-node/pull/39) 28 | 29 | ## 1.6.1 - 2024-03-04 30 | 31 | - **Fix:** Axios `require` error with some Node versions 32 | 33 | ## 1.6.0 - 2024-02-07 34 | 35 | - Updated npm depencies 36 | 37 | ## 1.5.0 - 2022-10-24 38 | 39 | - Added support for [Lists API](https://www.geocod.io/docs/#geocoding-lists) 40 | 41 | ## 1.4.0 - 2021-11-13 42 | 43 | - Added support for Geocodio API v1.7 44 | 45 | ## 1.3.3 - 2021-04-16 46 | ## 1.3.2 47 | 48 | - Updated dependencies and fixed package.json configuration error 49 | 50 | ## 1.3.1 - 2021-01-05 51 | 52 | - Updated dependencies 53 | 54 | ## 1.3.0 - 2020-06-03 55 | 56 | - Added support for Geocodio API v1.6 57 | 58 | ## 1.2.1 - 2020-05-14 59 | 60 | - Updated npm dependencies 61 | 62 | ## 1.2.0 - 2020-05-14 63 | 64 | - Added support for Geocodio API v1.5 65 | 66 | ## 1.1.0 - 2020-02-24 67 | 68 | - **Fix:** Fixed issue where batch requests did not allow custom keys 69 | 70 | ## 1.0.0 - 2019-09-19 71 | 72 | - First official release 73 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Dotsquare LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # geocod.io Node library [![NPM version][npm-image]][npm-url] 2 | > Library for performing forward and reverse address geocoding for addresses or coordinates in the US and Canada. 3 | 4 | 5 | 6 | - [Installation](#installation) 7 | - [Usage](#usage) 8 | * [Single geocoding](#single-geocoding) 9 | * [Batch geocoding](#batch-geocoding) 10 | * [Field appends](#field-appends) 11 | * [Address components](#address-components) 12 | * [Limit results](#limit-results) 13 | * [Lists](#lists) 14 | * [Create A List](#create-a-list) 15 | * [Get List Status](#get-list-status) 16 | * [Get All Lists](#get-all-lists) 17 | * [Download A List](#download-a-list) 18 | * [Delete A List](#delete-a-list) 19 | - [Testing](#testing) 20 | - [Changelog](#changelog) 21 | - [Security](#security) 22 | - [License](#license) 23 | 24 | 25 | 26 | ## Installation 27 | 28 | You can install the package via npm or yarn (pick one): 29 | 30 | ```bash 31 | $ npm install --save geocodio-library-node 32 | $ yarn add geocodio-library-node 33 | ``` 34 | 35 | ## Usage 36 | 37 | > Don't have an API key yet? Sign up at [https://dash.geocod.io](https://dash.geocod.io) to get an API key. The first 2,500 lookups per day are free. 38 | 39 | ### Single geocoding 40 | 41 | ```javascript 42 | const Geocodio = require('geocodio-library-node'); 43 | const geocoder = new Geocodio('YOUR_API_KEY'); 44 | // const geocoder = new Geocodio('YOUR_API_KEY', 'api.enterprise.geocod.io'); // optionally overwrite the API hostname 45 | 46 | geocoder 47 | .geocode('1109 N Highland St, Arlington, VA') 48 | .then(response => { 49 | console.log(response); 50 | }) 51 | /* 52 | response => { 53 | "input": { 54 | "address_components": { 55 | "number": "1109", 56 | "predirectional": "N", 57 | "street": "Highland", 58 | "suffix": "St", 59 | "formatted_street": "N Highland St", 60 | "city": "Arlington", 61 | "state": "VA", 62 | "country": "US" 63 | }, 64 | "formatted_address": "1109 N Highland St, Arlington, VA" 65 | }, 66 | "results": [ 67 | { 68 | "address_components": { 69 | "number": "1109", 70 | "predirectional": "N", 71 | "street": "Highland", 72 | "suffix": "St", 73 | "formatted_street": "N Highland St", 74 | "city": "Arlington", 75 | "county": "Arlington County", 76 | "state": "VA", 77 | "zip": "22201", 78 | "country": "US" 79 | }, 80 | "formatted_address": "1109 N Highland St, Arlington, VA 22201", 81 | "location": { 82 | "lat": 38.886672, 83 | "lng": -77.094735 84 | }, 85 | "accuracy": 1, 86 | "accuracy_type": "rooftop", 87 | "source": "Arlington" 88 | }, 89 | { 90 | "address_components": { 91 | "number": "1109", 92 | "predirectional": "N", 93 | "street": "Highland", 94 | "suffix": "St", 95 | "formatted_street": "N Highland St", 96 | "city": "Arlington", 97 | "county": "Arlington County", 98 | "state": "VA", 99 | "zip": "22201", 100 | "country": "US" 101 | }, 102 | "formatted_address": "1109 N Highland St, Arlington, VA 22201", 103 | "location": { 104 | "lat": 38.886665, 105 | "lng": -77.094733 106 | }, 107 | "accuracy": 1, 108 | "accuracy_type": "rooftop", 109 | "source": "Virginia Geographic Information Network (VGIN)" 110 | } 111 | ] 112 | } 113 | */ 114 | .catch(error => { 115 | console.error(error); 116 | }); 117 | 118 | geocoder.reverse('38.9002898,-76.9990361') 119 | .then(response => { ... }) 120 | .catch(err => { ... }); 121 | 122 | geocoder.reverse([38.9002898, -76.9990361]) 123 | .then(response => { ... }) 124 | .catch(err => { ... }); 125 | ``` 126 | 127 | > Note: You can read more about accuracy scores, accuracy types, input formats and more at https://www.geocod.io/docs/ 128 | 129 | ### Batch geocoding 130 | 131 | To batch geocode, simply pass an array of addresses or coordinates instead of a single string 132 | 133 | ```javascript 134 | geocoder.geocode([ 135 | '1109 N Highland St, Arlington VA', 136 | '525 University Ave, Toronto, ON, Canada', 137 | '4410 S Highway 17 92, Casselberry FL', 138 | '15000 NE 24th Street, Redmond WA', 139 | '17015 Walnut Grove Drive, Morgan Hill CA' 140 | ]) 141 | .then(response => { ... }) 142 | .catch(err => { ... }); 143 | 144 | geocoder.reverse([ 145 | '35.9746000,-77.9658000', 146 | '32.8793700,-96.6303900', 147 | '33.8337100,-117.8362320', 148 | '35.4171240,-80.6784760' 149 | ]) 150 | .then(response => { ... }) 151 | .catch(err => { ... }); 152 | 153 | // Optionally supply a custom key that will be returned along with results 154 | geocoder.geocode({ 155 | 'MyId1': '1109 N Highland St, Arlington VA', 156 | 'MyId2': '525 University Ave, Toronto, ON, Canada', 157 | 'MyId3': '4410 S Highway 17 92, Casselberry FL', 158 | 'MyId4': '15000 NE 24th Street, Redmond WA', 159 | 'MyId5': '17015 Walnut Grove Drive, Morgan Hill CA' 160 | }) 161 | .then(response => { ... }) 162 | .catch(err => { ... }); 163 | ``` 164 | 165 | ### Field appends 166 | 167 | Geocodio allows you to append additional data points such as congressional districts, census codes, timezone, ACS survey results and [much much more](https://www.geocod.io/docs/#fields). 168 | 169 | To request additional fields, simply supply them as an array as the second parameter 170 | 171 | ```javascript 172 | geocoder.geocode( 173 | [ 174 | '1109 N Highland St, Arlington VA', 175 | '525 University Ave, Toronto, ON, Canada' 176 | ], 177 | [ 'cd', 'timezone' ] 178 | ) 179 | .then(response => { ... }) 180 | .catch(err => { ... }); 181 | 182 | geocoder.reverse('38.9002898,-76.9990361', ['census2010']) 183 | .then(response => { ... }) 184 | .catch(err => { ... }); 185 | ``` 186 | 187 | ### Address components 188 | 189 | For forward geocoding requests it is possible to supply [individual address components](https://www.geocod.io/docs/#single-address) instead of a full address string. This works for both single and batch geocoding requests. 190 | 191 | ```javascript 192 | geocoder.geocode({ 193 | street: '1109 N Highland St', 194 | city: 'Arlington', 195 | state: 'VA', 196 | postal_code: '22201' 197 | }) 198 | .then(response => { ... }) 199 | .catch(err => { ... }); 200 | 201 | geocoder.geocode([ 202 | { 203 | street: '1109 N Highland St', 204 | city: 'Arlington', 205 | state: 'VA' 206 | }, 207 | { 208 | street: '525 University Ave', 209 | city: 'Toronto', 210 | state: 'ON', 211 | country: 'Canada', 212 | }, 213 | ]) 214 | .then(response => { ... }) 215 | .catch(err => { ... }); 216 | ``` 217 | 218 | ### Limit results 219 | 220 | Optionally limit the number of maximum geocoding results by using the third parameter on `geocode(...)` or `reverse(...)` 221 | 222 | ```javascript 223 | // Only get the frst result 224 | geocoder.geocode('1109 N Highland St, Arlington, VA', [], 1) 225 | .then(response => { ... }) 226 | .catch(err => { ... }); 227 | 228 | // Return up to 5 geocoding results 229 | geocoder.reverse('38.9002898,-76.9990361', ['timezone'], 5) 230 | .then(response => { ... }) 231 | .catch(err => { ... }); 232 | ``` 233 | 234 | ### Lists 235 | 236 | List methods are nested within `.list`. To access list methods, be sure to to run `geocoder.list` and then include the task method you would like to utilize. 237 | 238 | #### Create A List 239 | 240 | To create and upload a new list using an existing .CSV file, run `geocoder.list.create(...)` and pass in your filename/file path. 241 | 242 | You may also need to pass in some additional parameters: 243 | * Direction: Use the default string `"forward"`. 244 | * Format: Use the default string `"{{A}} {{B}} {{C}} {{D}}"` 245 | * Callback: A callback URL. 246 | 247 | ```javascript 248 | geocoder.list.create( 249 | `${__dirname}/stubs/sample_list.csv`, 250 | "forward", 251 | "{{A}} {{B}} {{C}} {{D}}", 252 | "https://example.com/my-callback" 253 | ) 254 | ``` 255 | 256 | #### Get List Status 257 | 258 | To retrieve the current status of your list, pass your list's ID into `geocoder.list.status(...)`. 259 | 260 | ```javascript 261 | geocoder.list.status(1234567) 262 | .then(response => { ... }) 263 | .catch(err => { ... }); 264 | ``` 265 | 266 | #### Get All Lists 267 | 268 | To retrieve all available lists, run `geocoder.list.all()`. You do not need to pass anything into this function. 269 | 270 | ```javascript 271 | geocoder.list.all() 272 | .then(response => { ... }) 273 | .catch(err => { ... }); 274 | ``` 275 | 276 | #### Download A List 277 | 278 | To download a list, run `geocoder.list.download(...)` and pass in the ID of the list you'd like to download, as well as a string that includes a filename. Be sure to include a `.csv` file extension. 279 | 280 | ```javascript 281 | geocoder.list.download(1234567, "geocoded_file.csv") 282 | .then(response => { ...}) 283 | .catch(err => { ... }); 284 | ``` 285 | 286 | #### Delete A List 287 | 288 | To delete a list, run `geocoder.list.deleteList(...)` and pass in the ID of the list you'd like to delete. 289 | 290 | ```javascript 291 | geocoder.list.delete(1234567) 292 | .then(response => { ... }) 293 | .catch(err => { ... }); 294 | ``` 295 | 296 | ## Testing 297 | 298 | ```bash 299 | $ npm test 300 | ``` 301 | 302 | ## Changelog 303 | 304 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 305 | 306 | ## Security 307 | 308 | If you discover any security related issues, please email security@geocod.io instead of using the issue tracker. 309 | 310 | ## License 311 | 312 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 313 | 314 | [npm-image]: https://img.shields.io/npm/v/geocodio-library-node 315 | [npm-url]: https://npmjs.org/package/geocodio-library-node 316 | -------------------------------------------------------------------------------- /lib/__tests__/geocodioLibraryNode.test.js: -------------------------------------------------------------------------------- 1 | const Geocodio = require("../index.js"); 2 | 3 | describe("geocoder", () => { 4 | const geocoder = new Geocodio(); 5 | 6 | it("Can single forward geocode", () => { 7 | expect.assertions(1); 8 | 9 | return geocoder 10 | .geocode("1109 N Highland St, Arlington VA") 11 | .then(response => { 12 | expect(response.results[0].formatted_address).toEqual( 13 | "1109 N Highland St, Arlington, VA 22201" 14 | ); 15 | }); 16 | }); 17 | 18 | it("Can single forward geocode components", () => { 19 | expect.assertions(1); 20 | 21 | return geocoder 22 | .geocode({ street: "1109 N Highland St", postal_code: "22201" }) 23 | .then(response => { 24 | expect(response.results[0].formatted_address).toEqual( 25 | "1109 N Highland St, Arlington, VA 22201" 26 | ); 27 | }); 28 | }); 29 | 30 | it("Can single reverse geocode: string", () => { 31 | expect.assertions(1); 32 | 33 | return geocoder.reverse("38.886665,-77.094733").then(response => { 34 | expect(response.results[0].formatted_address).toEqual( 35 | "1109 N Highland St, Arlington, VA 22201" 36 | ); 37 | }); 38 | }); 39 | 40 | it("Can single reverse geocode: array", () => { 41 | expect.assertions(1); 42 | 43 | return geocoder.reverse([38.886665, -77.094733]).then(response => { 44 | expect(response.results[0].formatted_address).toEqual( 45 | "1109 N Highland St, Arlington, VA 22201" 46 | ); 47 | }); 48 | }); 49 | 50 | it("Can batch forward geocode", () => { 51 | expect.assertions(2); 52 | 53 | return geocoder 54 | .geocode([ 55 | "1109 N Highland St, Arlington VA", 56 | "525 University Ave, Toronto, ON, Canada" 57 | ]) 58 | .then(response => { 59 | expect( 60 | response.results[0].response.results[0].formatted_address 61 | ).toEqual("1109 N Highland St, Arlington, VA 22201"); 62 | expect( 63 | response.results[1].response.results[0].formatted_address 64 | ).toEqual("525 University Ave, Toronto, ON M5G"); 65 | }); 66 | }); 67 | 68 | it("Can batch forward geocode with keys", () => { 69 | expect.assertions(2); 70 | 71 | return geocoder 72 | .geocode({ 73 | SomeIdentifier: "1109 N Highland St, Arlington VA", 74 | SomeOtherIdentifier: "525 University Ave, Toronto, ON, Canada" 75 | }) 76 | .then(response => { 77 | const res = response.results; 78 | 79 | expect( 80 | res.SomeIdentifier.response.results[0].formatted_address 81 | ).toEqual("1109 N Highland St, Arlington, VA 22201"); 82 | expect( 83 | res.SomeOtherIdentifier.response.results[0].formatted_address 84 | ).toEqual("525 University Ave, Toronto, ON M5G"); 85 | }); 86 | }); 87 | 88 | it("Can batch forward geocode components", () => { 89 | expect.assertions(2); 90 | 91 | return geocoder 92 | .geocode([ 93 | { street: "1109 N Highland St", postal_code: "22201" }, 94 | { 95 | street: "525 University Ave", 96 | city: "Toronto", 97 | state: "Ontario", 98 | country: "Canada" 99 | } 100 | ]) 101 | .then(response => { 102 | expect( 103 | response.results[0].response.results[0].formatted_address 104 | ).toEqual("1109 N Highland St, Arlington, VA 22201"); 105 | expect( 106 | response.results[1].response.results[0].formatted_address 107 | ).toEqual("525 University Ave, Toronto, ON M5G"); 108 | }); 109 | }); 110 | 111 | it("Can batch reverse geocode: string", () => { 112 | expect.assertions(2); 113 | 114 | return geocoder 115 | .reverse(["35.9746000,-77.9658000", "32.8793700,-96.6303900"]) 116 | .then(response => { 117 | expect( 118 | response.results[0].response.results[0].formatted_address 119 | ).toEqual("101 W Washington St, Nashville, NC 27856"); 120 | expect( 121 | response.results[1].response.results[0].formatted_address 122 | ).toEqual("3034 S 1st St, Garland, TX 75041"); 123 | }); 124 | }); 125 | 126 | it("Can batch reverse geocode: array", () => { 127 | expect.assertions(2); 128 | 129 | return geocoder 130 | .reverse([ 131 | ["35.9746000", "-77.9658000"], 132 | ["32.8793700", "-96.6303900"] 133 | ]) 134 | .then(response => { 135 | expect( 136 | response.results[0].response.results[0].formatted_address 137 | ).toEqual("101 W Washington St, Nashville, NC 27856"); 138 | expect( 139 | response.results[1].response.results[0].formatted_address 140 | ).toEqual("3034 S 1st St, Garland, TX 75041"); 141 | }); 142 | }); 143 | 144 | it("Can append fields: forward, single", () => { 145 | expect.assertions(1); 146 | 147 | return geocoder 148 | .geocode("1109 N Highland St, Arlington VA", ["timezone"]) 149 | .then(response => { 150 | expect(response.results[0].fields.timezone.abbreviation).toEqual("EST"); 151 | }); 152 | }); 153 | 154 | it("Can append fields: reverse, single", () => { 155 | expect.assertions(1); 156 | 157 | return geocoder 158 | .reverse("38.886665,-77.094733", ["timezone"]) 159 | .then(response => { 160 | expect(response.results[0].fields.timezone.abbreviation).toEqual("EST"); 161 | }); 162 | }); 163 | 164 | it("Can append fields: forward, batch", () => { 165 | expect.assertions(1); 166 | 167 | return geocoder 168 | .geocode(["1109 N Highland St, Arlington VA"], ["timezone"]) 169 | .then(response => { 170 | expect( 171 | response.results[0].response.results[0].fields.timezone.abbreviation 172 | ).toEqual("EST"); 173 | }); 174 | }); 175 | 176 | it("Can append fields: reverse, batch", () => { 177 | expect.assertions(1); 178 | 179 | return geocoder 180 | .reverse(["38.886665,-77.094733"], ["timezone"]) 181 | .then(response => { 182 | expect( 183 | response.results[0].response.results[0].fields.timezone.abbreviation 184 | ).toEqual("EST"); 185 | }); 186 | }); 187 | 188 | it("Can limit results", () => { 189 | expect.assertions(1); 190 | 191 | return geocoder.geocode("Arlington, VA").then(response => { 192 | expect(response.results.length).toBeGreaterThan(1); 193 | }); 194 | }); 195 | 196 | it("Can limit results", () => { 197 | expect.assertions(1); 198 | 199 | return geocoder.geocode("Arlington, VA", [], 1).then(response => { 200 | expect(response.results.length).toEqual(1); 201 | }); 202 | }); 203 | 204 | // LIST TESTS 205 | 206 | it("Can create a new list, check status, download list and delete list", () => { 207 | expect.assertions(1); 208 | 209 | // Upload and create list 210 | let newList = geocoder.list 211 | .create( 212 | `${__dirname}/stubs/sample_list.csv`, 213 | "forward", 214 | "{{A}} {{B}} {{C}} {{D}}", 215 | "https://example.com/my-callback" 216 | ) 217 | .then(response => { 218 | expect(response.file.filename).toEqual("sample_list.csv"); 219 | 220 | geocoder.list.status(response.id).then( 221 | setTimeout(resp => { 222 | expect(resp.id).toEqual(response.id); 223 | 224 | geocoder.list 225 | .download(response.id, "geocoded_file.csv") 226 | .then(resp => { 227 | expect(resp.file.filename).toEqual("geocoded_file.csv"); 228 | 229 | geocoder.list.delete(response.id).then(resp => { 230 | expect(resp.id).toEqual(null); 231 | }); 232 | }); 233 | }, 3000) 234 | ); 235 | }); 236 | 237 | return newList; 238 | }); 239 | }); 240 | -------------------------------------------------------------------------------- /lib/__tests__/geocodioLibraryNodeErrorHandling.test.js: -------------------------------------------------------------------------------- 1 | const Geocodio = require("../index.js"); 2 | 3 | describe("error handling", () => { 4 | it("Throws error with invalid API key", () => { 5 | expect.assertions(2); 6 | 7 | const geocoder = new Geocodio("BAD_API_KEY"); 8 | 9 | return geocoder.geocode("20003").catch(err => { 10 | expect(err).toBeInstanceOf(Error); 11 | expect(err.message).toEqual("Invalid API key"); 12 | }); 13 | }); 14 | 15 | it("Throws error with bad query", () => { 16 | expect.assertions(2); 17 | 18 | const geocoder = new Geocodio(); 19 | 20 | return geocoder.geocode(" ").catch(err => { 21 | expect(err).toBeInstanceOf(Error); 22 | expect(err.message).toEqual("Could not parse address"); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /lib/__tests__/stubs/sample_list.csv: -------------------------------------------------------------------------------- 1 | address,city,state,zip 2 | 660 Pennsylvania Ave SE,Washington,DC,20003 3 | 1718 14th St NW,Washington,DC,20009 4 | 1309 5th St NE,,,20002 5 | 2150 P St NW,,,20037 6 | 201 F Street NE,,,20002 7 | 1001 2nd St SE,,,20003 8 | 1645 Wisconsin Avenue NW,Washington,DC,20007 9 | 820 East Baltimore Street,Baltimore,MD,21202 10 | 800 F St NW,Washington,DC,20001 11 | 700 Constitution Avenue NW,Washington,DC,20565 12 | 1123 Pennsylvania Ave SE,Washington,DC,20003 13 | 621 Pennsylvania Ave SE,Washington,DC,20003 14 | 1702 G Street NW,Washington,DC,20006 15 | 701 8th St SE,Washington,DC,20003 16 | 12187 Darnestown Rd,Gaithersburg,MD,20878 17 | 4961 Elm Street,Bethesda,MD, 18 | 3064 Mount Pleasant St NW,Washington,DC, 19 | 1052 Thomas Jefferson Street NW,Washington,DC, 20 | 475 H St NW,Washington,DC, 21 | 1301 U St NW,Washington,DC, 22 | "1726 20th Street, NW",Washington,DC, 23 | "1916 I Street, NW",Washington,DC, 24 | 107 Church St NE,Vienna,VA, 25 | 4817 Bethesda Ave,Bethesda,MD,20814 -------------------------------------------------------------------------------- /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'geocodio-library-node' { 2 | export interface AddressComponents { 3 | number?: string; 4 | predirectional?: string; 5 | prefix?: string; 6 | street?: string; 7 | suffix?: string; 8 | postdirectional?: string; 9 | secondaryunit?: string; 10 | secondarynumber?: string; 11 | formatted_street?: string; 12 | city?: string; 13 | county?: string; 14 | state?: string; 15 | zip?: string; 16 | country?: string; 17 | postal_code?: string; // Alternative to zip used in some API calls 18 | } 19 | 20 | export type AddressInputComponents = Pick 21 | 22 | export type GeocodeAccuracyType = 23 | | 'rooftop' 24 | | 'point' 25 | | 'range_interpolation' 26 | | 'nearest_rooftop_match' 27 | | 'street_center' 28 | | 'place' 29 | | 'state'; 30 | 31 | export interface Location { 32 | lat: number; 33 | lng: number; 34 | } 35 | 36 | export interface Legislator { 37 | type: string; 38 | bio: { 39 | last_name: string; 40 | first_name: string; 41 | birthday: string; 42 | gender: string; 43 | party: string; 44 | }; 45 | contact: { 46 | url: string; 47 | address: string; 48 | phone: string; 49 | contact_form?: string; 50 | }; 51 | social: { 52 | rss_url?: string; 53 | twitter?: string; 54 | facebook?: string; 55 | youtube?: string; 56 | youtube_id?: string; 57 | }; 58 | references: { 59 | bioguide_id?: string; 60 | thomas_id?: string; 61 | opensecrets_id?: string; 62 | lis_id?: string; 63 | cspan_id?: string; 64 | govtrack_id?: string; 65 | votesmart_id?: string; 66 | ballotpedia_id?: string; 67 | washington_post_id?: string; 68 | icpsr_id?: string; 69 | wikipedia_id?: string; 70 | }; 71 | source: string; 72 | } 73 | 74 | export interface CongressionalDistrict { 75 | name: string; 76 | district_number: number; 77 | ocd_id: string; 78 | congress_number: string; 79 | congress_years: string; 80 | proportion: number; 81 | current_legislators: Legislator[]; 82 | } 83 | 84 | export interface StateLegislativeDistrict { 85 | name: string; 86 | district_number: string; 87 | ocd_id: string; 88 | is_upcoming_state_legislative_district: boolean; 89 | proportion: number; 90 | } 91 | 92 | export interface StateLegislativeDistricts { 93 | senate?: StateLegislativeDistrict[]; 94 | house?: StateLegislativeDistrict[]; 95 | } 96 | 97 | export interface SchoolDistrict { 98 | name: string; 99 | lea_code: string; 100 | grade_low: string; 101 | grade_high: string; 102 | } 103 | 104 | export interface SchoolDistricts { 105 | elementary?: SchoolDistrict; 106 | secondary?: SchoolDistrict; 107 | unified?: SchoolDistrict; 108 | } 109 | 110 | export interface Timezone { 111 | name: string; 112 | utc_offset: number; 113 | observes_dst: boolean; 114 | abbreviation: string; 115 | source: string; 116 | } 117 | 118 | export interface MetroArea { 119 | name: string; 120 | area_code: string; 121 | type?: 'metropolitan' | 'micropolitan'; 122 | } 123 | 124 | export interface StatisticalArea { 125 | name: string; 126 | area_code: string; 127 | } 128 | 129 | export interface Census { 130 | census_year: number; 131 | state_fips: string; 132 | county_fips: string; 133 | place_fips: string; 134 | tract_code: string; 135 | block_code: string; 136 | block_group: string; 137 | full_fips: string; 138 | metro_micro_statistical_area?: MetroArea; 139 | combined_statistical_area?: StatisticalArea; 140 | metropolitan_division?: StatisticalArea; 141 | source: string; 142 | } 143 | 144 | export interface RecordType { 145 | code: string; 146 | description: string; 147 | } 148 | 149 | export interface CarrierRoute { 150 | id: string; 151 | description: string; 152 | } 153 | 154 | export interface FacilityCode { 155 | code: string; 156 | description: string; 157 | } 158 | 159 | export interface Zip4 { 160 | record_type: RecordType; 161 | carrier_route: CarrierRoute; 162 | building_or_firm_name: string; 163 | plus4: string[]; 164 | zip9: string[]; 165 | government_building: boolean; 166 | facility_code: FacilityCode; 167 | city_delivery: boolean; 168 | valid_delivery_area: boolean; 169 | exact_match: boolean; 170 | } 171 | 172 | export interface Fields { 173 | congressional_districts?: CongressionalDistrict[]; 174 | state_legislative_districts?: StateLegislativeDistricts; 175 | school_districts?: SchoolDistricts; 176 | timezone?: Timezone; 177 | census?: Census; 178 | zip4?: Zip4; 179 | [key: string]: unknown; 180 | } 181 | 182 | export interface GeocodedAddress { 183 | address_components: AddressComponents; 184 | formatted_address: string; 185 | location: Location; 186 | accuracy: number; 187 | accuracy_type: GeocodeAccuracyType; 188 | source?: string; 189 | fields?: Fields; 190 | } 191 | 192 | export type FieldOption = 193 | | 'cd' 194 | | 'cd116' 195 | | 'cd115' 196 | | 'cd114' 197 | | 'cd113' 198 | | 'stateleg' 199 | | 'school' 200 | | 'timezone' 201 | | 'census' 202 | | 'census2000' 203 | | 'census2010' 204 | | 'census2011' 205 | | 'census2012' 206 | | 'census2013' 207 | | 'census2014' 208 | | 'census2015' 209 | | 'census2016' 210 | | 'census2017' 211 | | 'census2018' 212 | | 'census2019' 213 | | 'census2020' 214 | | 'provriding' 215 | | 'riding' 216 | | 'zip4' 217 | | 'acs-demographics' 218 | | 'acs-economics' 219 | | 'acs-families' 220 | | 'acs-housing' 221 | | 'acs-social'; 222 | 223 | export interface SingleGeocodeResponse { 224 | input: { 225 | address_components: AddressComponents; 226 | formatted_address: string; 227 | }; 228 | results: GeocodedAddress[]; 229 | _warnings?: string[]; 230 | } 231 | 232 | export interface BatchGeocodeResponse | Record> { 233 | results: T extends Array ? Array<{ 234 | query: Q; 235 | response: SingleGeocodeResponse; 236 | }> : Record; 239 | } 240 | 241 | export interface ReverseGeocodeResponse { 242 | results: GeocodedAddress[]; 243 | _warnings?: string[]; 244 | } 245 | 246 | export interface BatchReverseGeocodeResponse | Record> { 247 | results: T extends Array ? Array<{ 248 | query: Q; 249 | response: ReverseGeocodeResponse; 250 | }> : Record; 253 | } 254 | 255 | // List API types 256 | export interface ListResponse { 257 | id: number; 258 | file: { 259 | filename: string; 260 | }; 261 | } 262 | 263 | export default class Geocodio { 264 | constructor(apiKey?: string, hostname?: string, apiVersion?: string); 265 | 266 | geocode(query: string | AddressInputComponents, fields?: FieldOption[], limit?: number): Promise; 267 | geocode | Record>(query: T, fields?: FieldOption[], limit?: number): Promise>; 268 | reverse(query: string | [number, number], fields?: FieldOption[], limit?: number): Promise; 269 | reverse | Record>(query: T, fields?: FieldOption[], limit?: number): Promise>; 270 | 271 | list: { 272 | create(filename: string, direction: string, format: string, callback: string): Promise; 273 | status(listId: number): Promise; 274 | all(): Promise; 275 | download(listId: number, output: string): Promise; 276 | delete(listId: number): Promise; 277 | deleteList(listId: number): Promise; // Alias for delete 278 | }; 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const axios = require("axios"); 4 | const fs = require("fs"); 5 | const FormData = require("form-data"); 6 | 7 | class Geocodio { 8 | constructor(apiKey, hostname, apiVersion) { 9 | this.apiKey = apiKey || process.env.GEOCODIO_API_KEY || null; 10 | this.hostname = 11 | hostname || process.env.GEOCODIO_HOSTNAME || "api.geocod.io"; 12 | this.apiVersion = apiVersion || process.env.GEOCODIO_API_VERSION || "v1.8"; 13 | 14 | this.SINGLE_TIMEOUT_MS = 5000; 15 | this.BATCH_TIMEOUT_MS = 30 * 60 * 1000; 16 | 17 | this.HTTP_HEADERS = { 18 | "User-Agent": "geocodio-library-node/1.10.0", 19 | Authorization: `Bearer ${this.apiKey}` 20 | }; 21 | 22 | this.ADDRESS_COMPONENT_PARAMETERS = [ 23 | "street", 24 | "city", 25 | "state", 26 | "postal_code", 27 | "country" 28 | ]; 29 | 30 | this.list = this.list(); 31 | } 32 | 33 | geocode(query, fields = [], limit = null) { 34 | return this.handleRequest("geocode", query, fields, limit); 35 | } 36 | 37 | reverse(query, fields = [], limit = null) { 38 | return this.handleRequest("reverse", query, fields, limit); 39 | } 40 | 41 | handleRequest(endpoint, query, fields = [], limit = null) { 42 | const url = this.formatUrl(endpoint); 43 | 44 | let queryParameters = { 45 | fields: fields.join(",") 46 | }; 47 | 48 | if (limit) { 49 | queryParameters.limit = limit; 50 | } 51 | 52 | query = this.preprocessQuery(query, endpoint); 53 | 54 | let response = null; 55 | if (this.isSingleQuery(query)) { 56 | response = this.performSingleRequest(url, query, queryParameters); 57 | } else { 58 | query = this.preprocessQueryList(query, endpoint); 59 | response = this.performBatchRequest(url, query, queryParameters); 60 | } 61 | 62 | return this.handleResponse(response); 63 | } 64 | 65 | handleResponse(response) { 66 | return response 67 | .then(response => response.data) 68 | .catch(error => { 69 | if (error.response) { 70 | const errorMessage = 71 | error.response.data.message || error.response.data.error || "Error"; 72 | const code = error.response.status || 0; 73 | 74 | const decoratedError = new Error(errorMessage); 75 | decoratedError.code = code; 76 | 77 | throw decoratedError; 78 | } else { 79 | throw error; 80 | } 81 | }); 82 | } 83 | 84 | formatUrl(endpoint) { 85 | return `https://${this.hostname}/${this.apiVersion}/${endpoint}`; 86 | } 87 | 88 | preprocessQueryList(query, endpoint) { 89 | for (const key in query) { 90 | if ( 91 | Array.isArray(query) || 92 | Object.prototype.hasOwnProperty.call(query, key) 93 | ) { 94 | query[key] = this.preprocessQuery(query[key], endpoint); 95 | } 96 | } 97 | 98 | return query; 99 | } 100 | 101 | preprocessQuery(query, endpoint) { 102 | // Convert lat/lon to a comma-separated string 103 | const queryIsCoordinateArray = Array.isArray(query) && query.length === 2; 104 | 105 | if (endpoint === "reverse" && queryIsCoordinateArray) { 106 | const [latitude, longitude] = query; 107 | 108 | if (this.isNumeric(latitude) && this.isNumeric(longitude)) { 109 | query = `${latitude},${longitude}`; 110 | } 111 | } 112 | 113 | return query; 114 | } 115 | 116 | isSingleQuery(query) { 117 | if (typeof query === "object") { 118 | const addressComponentKeys = Object.keys(query).filter(value => 119 | this.ADDRESS_COMPONENT_PARAMETERS.includes(value) 120 | ); 121 | 122 | return addressComponentKeys.length >= 1; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | performSingleRequest(url, query, queryParameters) { 129 | if (typeof query === "object") { 130 | queryParameters = { 131 | ...queryParameters, 132 | ...query 133 | }; 134 | } else { 135 | queryParameters.q = query; 136 | } 137 | 138 | return axios.get(url, { 139 | params: queryParameters, 140 | timeout: this.SINGLE_TIMEOUT_MS, 141 | headers: this.HTTP_HEADERS 142 | }); 143 | } 144 | 145 | performBatchRequest(url, queries, queryParameters) { 146 | return axios.post(url, queries, { 147 | params: queryParameters, 148 | timeout: this.BATCH_TIMEOUT_MS, 149 | headers: this.HTTP_HEADERS 150 | }); 151 | } 152 | 153 | isNumeric(n) { 154 | return !isNaN(parseFloat(n)) && isFinite(n); 155 | } 156 | 157 | list() { 158 | const create = (filename, direction, format, callback) => { 159 | const form = new FormData(); 160 | form.append("file", fs.createReadStream(filename)); 161 | 162 | const headers = { 163 | ...this.HTTP_HEADERS, 164 | ...form.getHeaders() 165 | }; 166 | 167 | return this.handleResponse( 168 | axios.post(this.formatUrl("lists"), form, { 169 | params: { 170 | direction, 171 | format, 172 | callback 173 | }, 174 | headers: headers 175 | }) 176 | ); 177 | }; 178 | 179 | const status = listId => { 180 | return this.handleResponse( 181 | axios.get(this.formatUrl(`lists/${listId}`), { 182 | headers: this.HTTP_HEADERS 183 | }) 184 | ); 185 | }; 186 | 187 | const all = () => { 188 | return this.handleResponse( 189 | axios.get(this.formatUrl(`lists`), { 190 | headers: this.HTTP_HEADERS 191 | }) 192 | ); 193 | }; 194 | 195 | const download = (listId, output) => { 196 | const writer = fs.createWriteStream(output); 197 | 198 | return this.handleResponse( 199 | axios({ 200 | method: "get", 201 | url: this.formatUrl(`lists/${listId}/download`), 202 | headers: this.HTTP_HEADERS, 203 | responseType: "stream" 204 | }).then(response => { 205 | return new Promise((resolve, reject) => { 206 | response.data.pipe(writer); 207 | let error = null; 208 | writer.on("error", err => { 209 | error = err; 210 | writer.close(); 211 | reject(err); 212 | }); 213 | writer.on("close", () => { 214 | if (!error) { 215 | resolve(true); 216 | } 217 | }); 218 | }); 219 | }) 220 | ); 221 | }; 222 | 223 | const deleteList = listId => { 224 | return this.handleResponse( 225 | axios.delete(this.formatUrl(`lists/${listId}`), { 226 | headers: this.HTTP_HEADERS 227 | }) 228 | ); 229 | }; 230 | 231 | return { 232 | create, 233 | status, 234 | all, 235 | download, 236 | deleteList, // Kept for backwards compatibility 237 | delete: deleteList 238 | }; 239 | } 240 | } 241 | 242 | module.exports = Geocodio; 243 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geocodio-library-node", 3 | "version": "1.11.0", 4 | "description": "geocod.io geocoding API library", 5 | "homepage": "https://github.com/Geocodio/geocodio-library-node", 6 | "author": { 7 | "name": "Mathias Hansen", 8 | "email": "mathias@geocod.io", 9 | "url": "https://www.geocod.io" 10 | }, 11 | "files": [ 12 | "lib" 13 | ], 14 | "main": "lib/index.js", 15 | "types": "lib/index.d.ts", 16 | "keywords": [ 17 | "geocoding", 18 | "geocode", 19 | "geo", 20 | "address", 21 | "congress", 22 | "timezone", 23 | "census" 24 | ], 25 | "devDependencies": { 26 | "eslint": "^6.8.0", 27 | "eslint-config-prettier": "^6.15.0", 28 | "eslint-config-xo": "^0.26.0", 29 | "eslint-plugin-prettier": "^3.4.0", 30 | "husky": "^3.1.0", 31 | "jest": "^29.7.0", 32 | "lint-staged": "^9.5.0", 33 | "prettier": "^1.19.1" 34 | }, 35 | "engines": { 36 | "node": ">= 8.0.0" 37 | }, 38 | "lint-staged": { 39 | "*.js": [ 40 | "eslint --fix", 41 | "git add" 42 | ], 43 | "*.json": [ 44 | "prettier --write", 45 | "git add" 46 | ] 47 | }, 48 | "eslintConfig": { 49 | "extends": [ 50 | "xo", 51 | "prettier" 52 | ], 53 | "env": { 54 | "jest": true, 55 | "node": true 56 | }, 57 | "rules": { 58 | "prettier/prettier": "error", 59 | "camelcase": 0 60 | }, 61 | "plugins": [ 62 | "prettier" 63 | ] 64 | }, 65 | "scripts": { 66 | "pretest": "eslint .", 67 | "fix": "eslint . --fix", 68 | "test": "jest", 69 | "toc": "markdown-toc README.md -i" 70 | }, 71 | "repository": "Geocodio/geocodio-library-node", 72 | "jest": { 73 | "moduleNameMapper": { 74 | "axios": "/node_modules/axios/dist/node/axios.cjs" 75 | }, 76 | "testEnvironment": "node" 77 | }, 78 | "license": "MIT", 79 | "dependencies": { 80 | "axios": "^1.7.4", 81 | "form-data": "^4.0.0" 82 | } 83 | } 84 | --------------------------------------------------------------------------------