Writing Basic Request Code
# Writing Basic Request Code
In this lesson, we start writing the ts-axios library. Our goal is to implement simple request sending functionality -- the client sends a request to the server via the XMLHttpRequest object, and the server can receive and respond to the request.
We'll implement the most basic operation of axios, sending a request by passing in an object, like this:
axios({
method: 'get',
url: '/simple/get',
params: {
a: 1,
b: 2
}
})
2
3
4
5
6
7
8
# Creating the Entry File
We delete the files in the src directory and first create an index.ts file as the entry file for the entire library. Then we define an axios method and export it:
function axios(config) {
}
export default axios
2
3
4
5
6
7
Here the TypeScript compiler will flag errors: an implicit any error on the config declaration, and an empty code block. The empty code block is straightforward. The first error occurs because we set strict to true in the TypeScript compilation config.
# Compilation Configuration File tsconfig.json
The tsconfig.json file specifies the root files and compilation options for the project. For detailed learning, I recommend visiting the official site (opens new window) for a systematic study.
When we covered TypeScript basics earlier, we ran the tsc command to compile TypeScript files. The compiler looks for the tsconfig.json file starting from the current directory and uses it for compilation options.
Let's look at the tsconfig.json file -- it contains many compilation settings. We set strict to true, which is equivalent to enabling all strict type checking options. Enabling --strict is equivalent to enabling --noImplicitAny, --noImplicitThis, --alwaysStrict, --strictNullChecks, --strictFunctionTypes, and --strictPropertyInitialization.
We mentioned --strictNullChecks when covering TypeScript basics. For other compilation options, I recommend checking the official documentation (opens new window) as a reference manual.
# Defining the AxiosRequestConfig Interface Type
Next, we need to define an interface type for the config parameter. We create a types directory with an index.ts file inside it, serving as the shared type definition file for the project.
Let's define the AxiosRequestConfig interface type:
export interface AxiosRequestConfig {
url: string
method?: string
data?: any
params?: any
}
2
3
4
5
6
Here, url is the request address and is required; the rest are optional properties. method is the HTTP method of the request; data is the data for post, patch, and similar request types, placed in the request body; params is the data for get, head, and similar request types, appended to the URL's query string.
To ensure method only accepts valid strings, we define a string literal type Method:
export type Method = 'get' | 'GET'
| 'delete' | 'Delete'
| 'head' | 'HEAD'
| 'options' | 'OPTIONS'
| 'post' | 'POST'
| 'put' | 'PUT'
| 'patch' | 'PATCH'
2
3
4
5
6
7
Then we change the method property type in AxiosRequestConfig to this string literal type:
export interface AxiosRequestConfig {
url: string
method?: Method
data?: any
params?: any
}
2
3
4
5
6
Back in index.ts, we import the AxiosRequestConfig type as the parameter type for config:
import { AxiosRequestConfig } from './types'
function axios(config: AxiosRequestConfig) {
}
export default axios
2
3
4
5
6
Next, let's implement the logic inside this function body -- sending the request.
# Sending Requests with XMLHttpRequest
We don't want to implement the request sending logic directly in index.ts. Following modular programming principles, we extract this functionality into a separate module.
So we create an xhr.ts file in the src directory. We export an xhr method that accepts a config parameter of type AxiosRequestConfig.
import { AxiosRequestConfig } from './types'
export default function xhr(config: AxiosRequestConfig) {
}
2
3
4
Next, let's implement the function body logic:
export default function xhr(config: AxiosRequestConfig): void {
const { data = null, url, method = 'get' } = config
const request = new XMLHttpRequest()
request.open(method.toUpperCase(), url, true)
request.send(data)
}
2
3
4
5
6
7
8
9
First, we use destructuring assignment to extract the corresponding property values from config and assign them to variables, with some default values defined since some properties in the AxiosRequestConfig interface are optional.
Then we instantiate an XMLHttpRequest object, call its open method with the corresponding parameters, and finally call the send method to send the request.
For learning about XMLHttpRequest, I recommend studying its properties and methods systematically on MDN (opens new window) as a reference, since we may frequently consult this documentation during development.
# Importing the xhr Module
After writing the xhr module, we need to import it in index.ts:
import { AxiosRequestConfig } from './types'
import xhr from './xhr'
function axios(config: AxiosRequestConfig): void {
xhr(config)
}
export default axios
2
3
4
5
6
7
8
With that, our basic request sending code is complete. Next, let's write a small demo to use our axios library to send requests.
# Writing the Demo
We'll use Node.js's express (opens new window) library to run our demo, and webpack (opens new window) as the build tool.
# Installing Dependencies
First, let's install the dependencies needed for writing the demo:
"webpack": "^4.28.4",
"webpack-dev-middleware": "^3.5.0",
"webpack-hot-middleware": "^2.24.3",
"ts-loader": "^5.3.3",
"tslint-loader": "^3.5.4",
"express": "^4.16.4",
"body-parser": "^1.18.3"
2
3
4
5
6
7
Here, webpack is the bundling tool, webpack-dev-middleware and webpack-hot-middleware are two express middleware for webpack, ts-loader and tslint-loader are TypeScript-related loaders for webpack, express is the Node.js server framework, and body-parser is an express middleware for parsing body data.
# Writing the webpack Configuration File
Create the webpack configuration file webpack.config.js in the examples directory:
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
module.exports = {
mode: 'development',
/**
* We will create multiple subdirectories under the examples directory
* Different chapter demos will be placed in different subdirectories
* Each subdirectory will have an app.ts file
* app.ts serves as the webpack build entry file
* entries collects multiple directory entry files, and each entry also includes a file for hot module replacement
* entries is an object where the key is the directory name
*/
entry: fs.readdirSync(__dirname).reduce((entries, dir) => {
const fullDir = path.join(__dirname, dir)
const entry = path.join(fullDir, 'app.ts')
if (fs.statSync(fullDir).isDirectory() && fs.existsSync(entry)) {
entries[dir] = ['webpack-hot-middleware/client', entry]
}
return entries
}, {}),
/**
* Bundle and output target JS based on directory names, matching the directory name
*/
output: {
path: path.join(__dirname, '__build__'),
filename: '[name].js',
publicPath: '/__build__/'
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
use: [
{
loader: 'tslint-loader'
}
]
},
{
test: /\.tsx?$/,
use: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
}
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin()
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# Writing the Server File
Create server.js in the examples directory:
const express = require('express')
const bodyParser = require('body-parser')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')
const WebpackConfig = require('./webpack.config')
const app = express()
const compiler = webpack(WebpackConfig)
app.use(webpackDevMiddleware(compiler, {
publicPath: '/__build__/',
stats: {
colors: true,
chunks: false
}
}))
app.use(webpackHotMiddleware(compiler))
app.use(express.static(__dirname))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
const port = process.env.PORT || 8080
module.exports = app.listen(port, () => {
console.log(`Server listening on http://localhost:${port}, Ctrl+C to stop`)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Writing the Demo Code
First, create index.html and global.css in the examples directory as the entry file and global stylesheet for all demos.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ts-axios examples</title>
<link rel="stylesheet" href="/global.css">
</head>
<body style="padding: 0 20px">
<h1>ts-axios examples</h1>
<ul>
<li><a href="simple">Simple</a></li>
</ul>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
global.css:
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
color: #2c3e50;
}
ul {
line-height: 1.5em;
padding-left: 1.5em;
}
a {
color: #7f8c8d;
text-decoration: none;
}
a:hover {
color: #4fc08d;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Then create a simple directory in examples as this chapter's demo directory, and create index.html and app.ts files inside it.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Simple example</title>
</head>
<body>
<script src="/__build__/simple.js"></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
app.ts:
import axios from '../../src/index'
axios({
method: 'get',
url: '/simple/get',
params: {
a: 1,
b: 2
}
})
2
3
4
5
6
7
8
9
10
Since we're sending a request via axios, our server needs to implement the corresponding route. Let's modify server.js and add the following code:
const router = express.Router()
router.get('/simple/get', function(req, res) {
res.json({
msg: `hello world`
})
})
app.use(router)
2
3
4
5
6
7
8
9
# Running the Demo
Next, add a new npm script in package.json:
"dev": "node examples/server.js"
Then run the following command in the console:
npm run dev
This executes node examples/server.js, which starts our server.
Then open Chrome browser and visit http://localhost:8080/ to access our demo. Click into the Simple directory, and through the network tab in Developer Tools, you can see a request was successfully sent and the server's response data is visible.
With that, we've implemented simple request sending and written a demo for it. But there are some problems: the params data we passed in isn't being used or appended to the URL; we haven't handled the request body data format or request headers; and although we received response data at the network level, our code doesn't handle the response data either. In the next chapter, we'll address these issues.