Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Experimental API support (#7296)
* First basic API support

* Change require functionality

* Change 501 to 404

* Change wording

* Fix test
  • Loading branch information
huv1k authored and timneutkens committed May 11, 2019
1 parent 9f09299 commit fedfbd9
Show file tree
Hide file tree
Showing 10 changed files with 143 additions and 2 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -63,6 +63,7 @@
"@babel/preset-flow": "7.0.0",
"@babel/preset-react": "7.0.0",
"@mdx-js/loader": "0.18.0",
"@types/jest": "24.0.12",
"@types/string-hash": "1.1.1",
"@zeit/next-css": "1.0.2-canary.2",
"@zeit/next-sass": "1.0.2-canary.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/next-server/server/load-components.ts
Expand Up @@ -3,7 +3,7 @@ import { join } from 'path';

import { requirePage } from './require';

function interopDefault(mod: any) {
export function interopDefault(mod: any) {
return mod.default || mod
}

Expand Down
36 changes: 35 additions & 1 deletion packages/next-server/server/next-server.ts
Expand Up @@ -21,7 +21,8 @@ import {
PAGES_MANIFEST,
} from '../lib/constants'
import * as envConfig from '../lib/runtime-config'
import { loadComponents } from './load-components'
import { loadComponents, interopDefault } from './load-components'
import { getPagePath } from './require';

type NextConfig = any

Expand Down Expand Up @@ -199,6 +200,13 @@ export default class Server {
await this.serveStatic(req, res, p, parsedUrl)
},
},
{
match: route('/api/:path*'),
fn: async (req, res, params, parsedUrl) => {
const { pathname } = parsedUrl
await this.handleApiRequest(req, res, pathname!)
},
},
]

if (fs.existsSync(this.publicDir)) {
Expand Down Expand Up @@ -226,6 +234,32 @@ export default class Server {
return routes
}

/**
* Resolves `API` request, in development builds on demand
* @param req http request
* @param res http response
* @param pathname path of request
*/
private async handleApiRequest(req: IncomingMessage, res: ServerResponse, pathname: string) {
const resolverFunction = await this.resolveApiRequest(pathname)
if (resolverFunction === null) {
res.statusCode = 404
res.end('Not Found')
return
}

const resolver = interopDefault(require(resolverFunction))
resolver(req, res)
}

/**
* Resolves path to resolver function
* @param pathname path of request
*/
private resolveApiRequest(pathname: string) {
return getPagePath(pathname, this.distDir)
}

private generatePublicRoutes(): Route[] {
const routes: Route[] = []
const publicFiles = recursiveReadDirSync(this.publicDir)
Expand Down
17 changes: 17 additions & 0 deletions packages/next/server/next-dev-server.js
Expand Up @@ -141,6 +141,23 @@ export default class DevServer extends Server {
return !snippet.includes('data-amp-development-mode-only')
}

/**
* Check if resolver function is build or request new build for this function
* @param {string} pathname
*/
async resolveApiRequest (pathname) {
try {
await this.hotReloader.ensurePage(pathname)
} catch (err) {
// API route dosn't exist => return 404
if (err.code === 'ENOENT') {
return null
}
}
const resolvedPath = await super.resolveApiRequest(pathname)
return resolvedPath
}

async renderToHTML (req, res, pathname, query, options = {}) {
const compilationErr = await this.getCompilationError(pathname)
if (compilationErr) {
Expand Down
6 changes: 6 additions & 0 deletions test/integration/api-support/pages/api/posts/index.js
@@ -0,0 +1,6 @@
export default (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' })

const json = JSON.stringify([{ title: 'Cool Post!' }])
res.end(json)
}
15 changes: 15 additions & 0 deletions test/integration/api-support/pages/api/users.js
@@ -0,0 +1,15 @@
import url from 'url'

export default (req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' })

const { query } = url.parse(req.url, true)

const users = [{ name: 'Tim' }, { name: 'Jon' }]

const response =
query && query.name ? users.filter(user => user.name === query.name) : users

const json = JSON.stringify(response)
res.end(json)
}
1 change: 1 addition & 0 deletions test/integration/api-support/pages/index.js
@@ -0,0 +1 @@
export default () => <div>API - support</div>
1 change: 1 addition & 0 deletions test/integration/api-support/pages/user.js
@@ -0,0 +1 @@
export default () => <div>API - support</div>
54 changes: 54 additions & 0 deletions test/integration/api-support/test/index.test.js
@@ -0,0 +1,54 @@
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
import {
killApp,
findPort,
launchApp,
fetchViaHTTP
// renderViaHTTP,
} from 'next-test-utils'

const appDir = join(__dirname, '../')
let appPort
let server
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2

describe('API support', () => {
beforeAll(async () => {
appPort = await findPort()
server = await launchApp(appDir, appPort)
})
afterAll(() => killApp(server))

it('API request to undefined path', async () => {
const { status } = await fetchViaHTTP(appPort, '/api/unexisting', null, {
Accept: 'application/json'
})
expect(status).toEqual(404)
})

it('API request to list of users', async () => {
const data = await fetchViaHTTP(appPort, '/api/users', null, {
Accept: 'application/json'
}).then(res => res.ok && res.json())

expect(data).toEqual([{ name: 'Tim' }, { name: 'Jon' }])
})

it('API request to list of users with query parameter', async () => {
const data = await fetchViaHTTP(appPort, '/api/users?name=Tim', null, {
Accept: 'application/json'
}).then(res => res.ok && res.json())

expect(data).toEqual([{ name: 'Tim' }])
})

it('API request to nested posts', async () => {
const data = await fetchViaHTTP(appPort, '/api/posts', null, {
Accept: 'application/json'
}).then(res => res.ok && res.json())

expect(data).toEqual([{ title: 'Cool Post!' }])
})
})
12 changes: 12 additions & 0 deletions yarn.lock
Expand Up @@ -1589,6 +1589,18 @@
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.0.tgz#2cc2ca41051498382b43157c8227fea60363f94a"
integrity sha512-ohkhb9LehJy+PA40rDtGAji61NCgdtKLAlFoYp4cnuuQEswwdK3vz9SOIkkyc3wrk8dzjphQApNs56yyXLStaQ==

"@types/jest-diff@*":
version "20.0.1"
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==

"@types/jest@24.0.12":
version "24.0.12"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.12.tgz#0553dd0a5ac744e7dc4e8700da6d3baedbde3e8f"
integrity sha512-60sjqMhat7i7XntZckcSGV8iREJyXXI6yFHZkSZvCPUeOnEJ/VP1rU/WpEWQ56mvoh8NhC+sfKAuJRTyGtCOow==
dependencies:
"@types/jest-diff" "*"

"@types/loader-utils@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@types/loader-utils/-/loader-utils-1.1.3.tgz#82b9163f2ead596c68a8c03e450fbd6e089df401"
Expand Down

0 comments on commit fedfbd9

Please sign in to comment.