REST API built with Express.js exposing an Azure SQL Database via Azure App Service.
- Azure SQL Database
- TypeScript 3.3.1
- Express 4.17.1
- Express validator 6.4.0
- CORS 2.8.5
- Connect slashes 1.4.0
-
Clone the repo
git clone https://github.com/dreadnallen/azure-movies-api.git
-
Install NPM packages
npm install
-
Add environment variables to
/.env
DBUser={your-username} DBPassword={your-password} DBServer={your-servername.database.windows.net}
-
Run in development mode
npm run dev
-
Go to Azure Extension / App Service and click + sign to Create New Web App... Choose name and platform (Windows)
-
Go to Azure Portal and click "App Services" to find your new App Service:
- Click Deployment Center under Deployment, choose GitHub under Continous Deployment and click Authorize
- Log in to your GitHub account and click Continue
- Click App Service Build Service and click Continue
- Choose organization (your GitHub profile name), Repository and master branch
- Click Countinue and Finish. Automatic deployment is now activated.
- Click Configuration under Settings and click New application setting to add environment variables
DBUser
,DBPassword
andDBServer
.
Alternatives: Deploy using Azure CLI or Visual Studio Code
-
Change your
package.json
setting to:... "scripts": { "start":"node build/server.js", "postinstall": "tsc" } ...
-
Make sure typescript and @types/... are all in your
package.json
dependencies (npm install ... --save
) -
Install the kuduscript tool globally (source)
npm install kuduscript -g
-
After installing Kuduscript, open a CMD window on your application’s root and type the following command to generate files
.deployment
anddeploy.cmd
in your application root directory (mind the dot:.
):kuduscript -y --node --sitePath .
-
Look for these lines in
deploy.cmd
:: 3. Install npm packages IF EXIST "%DEPLOYMENT_TARGET%\package.json" ( pushd "%DEPLOYMENT_TARGET%" call :ExecuteCmd !NPM_CMD! install --production IF !ERRORLEVEL! NEQ 0 goto error popd )
and add the following directly after them: (source)
:: 4. Compile TypeScript echo Transpiling TypeScript in %DEPLOYMENT_TARGET%...call :ExecuteCmd node %DEPLOYMENT_TARGET%\node_modules\typescript\bin\tsc -p "%DEPLOYMENT_TARGET%"
-
Make the following changes to
web.config
in order to point Node to youroutDir
directory setting intsconfig.json
:... <handlers> <add name="iisnode" path="build/server.js" verb="*" modules="iisnode"/> </handlers> ... <rule name="DynamicContent"> <conditions> <add input="{{REQUEST_FILENAME}}" matchType="IsFile" negate="True"/> </conditions> <action type="Rewrite" url="build/server.js"/> </rule>
-
Make the following change to
web.config
in order to make your Node app catch any http errors (404
and500
e.g.): (source)... <httpErrors existingResponse="PassThrough"/> ...
GET /v1/genres
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/genres'
200 - OK
[
{
"id": 1,
"name": "{name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/1"
}
}
]
},
{
"id": 2,
"name": "{name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/2"
}
}
]
}
]
GET /v1/genres/{genre-id}
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/genres'/{genre-id}
property | type | |
---|---|---|
genre-id | integer | requried |
200 - OK
{
"id": 1,
"name": "{name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/1"
}
}
]
}
POST /v1/genres
$ curl --location --request POST 'https://azure-movies-api.azurewebsites.net/api/v1/genres' \
--data-raw '{
"name": "{name}"
}'
{
"name": "{name}"
}
property | type | |
---|---|---|
name | string | required |
201 - Created
{
"id": 1,
"name": "{name}"
}
PUT /v1/genres/{genre-id}
$ curl --location --request PUT 'https://azure-movies-api.azurewebsites.net/api/v1/genres/{genre-id}' \
--data-raw '{
"name": "{name}"
}'
property | type | |
---|---|---|
genre-id | integer | required |
{
"name": "{name}"
}
property | type | |
---|---|---|
name | string | required |
200 - OK
{
"id": 1,
"name": "{name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}"
}
DELETE /v1/genres/{genre-id}
$ curl --location --request DELETE 'https://azure-movies-api.azurewebsites.net/api/v1/genres/{genre-id}
property | type | |
---|---|---|
genre-id | integer | required |
200 - OK
{
"id": 1
}
GET /v1/movies?genreId={genre-id}
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/movies?genreId=5'
property | type | |
---|---|---|
genre-id | integer | optional |
200 - OK
[
{
"id": 1,
"name": "{name}",
"description": "{description}",
"image-url": "{image-url}",
"production-year": 2020,
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"movie-genre": [
{
"id": 5,
"name": "{genre-name}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/5"
}
}
]
},
{
"id": 6,
"name": "{genre-name}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/6"
}
}
]
}
],
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/1"
}
}
]
},
{
"id": 2,
"name": "{name}",
"description": "{description}",
"image-url": "{image-url}",
"production-year": 2020,
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"movie-genre": [
{
"id": 5,
"name": "{genre-name}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/5"
}
}
]
}
],
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/2"
}
}
]
}
]
GET /v1/movies/{movie-id}
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/movies/76'
property | type | |
---|---|---|
movie-id | integer | required |
200 - OK
{
"id": 1,
"name": "{name}",
"description": "{description}",
"image-url": "{image-url}",
"production-year": 2020,
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"movie-genre": [
{
"id": 5,
"name": "{genre-name}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/5"
}
}
]
},
{
"id": 6,
"name": "{genre-name}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/genres/6"
}
}
]
}
],
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/1"
}
}
]
}
POST /v1/movies
$ curl --location --request POST 'https://azure-movies-api.azurewebsites.net/api/v1/movies/' \
--data-raw '{
"name": "{name}",
"description": "{description}",
"production-year": 2020,
"image-url": "http://example.com",
"movie-genre": [
{
"id": 5
},
{
"id": 6
},
{
"id": 7
}
]
}'
{
"name": "{name}",
"description": "{name}",
"production-year": 2020,
"image-url": "http://example.com",
"movie-genre": [
{
"id": 5
},
{
"id": 6
},
{
"id": 7
}
]
}
property | type | |
---|---|---|
name | string | required |
description | string | required |
production-year | integer | required |
image-url | string | required |
movie-genre | (list of genre-ids) | optional |
201 - Created
{
"id": 1,
"name": "{name}",
"description": "{description}",
"image-url": "http://example.com",
"production-year": 2020,
"movie-genre": [
{
"id": 5,
"name": "{name}"
},
{
"id": 6,
"name": "{name}"
},
{
"id": 7,
"name": "{name}"
}
],
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/1"
}
}
]
}
PUT /v1/movies/{movie-id}
$ curl --location --request PUT 'https://azure-movies-api.azurewebsites.net/api/v1/movies/1' \
--data-raw '{
"id":463,
"name": "{name}",
"description": "{description}",
"production-year": 2020,
"image-url": "http://example.com",
"movie-genre": [
{
"id": 7
},
{
"id": 8
}
]
}'
{
"id": 463,
"name": "{name}",
"description": "{description}",
"production-year": 2020,
"image-url": "http://example.com",
"movie-genre": [
{
"id": 7
},
{
"id": 8
}
]
}
property | type | |
---|---|---|
id | integer | required |
name | string | required |
description | string | required |
production-year | integer | required |
image-url | string | required |
movie-genre | (list of genre-ids) | optional |
200 - OK
{
"id": 1,
"name": "{name}",
"description": "{description}",
"image-url": "http://example.com",
"production-year": 2020,
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"movie-genre": [
{
"id": 7,
"name": "{name}"
},
{
"id": 8,
"name": "{name}"
}
],
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/1"
}
}
]
}
DELETE /v1/movies/{movie-id}
$ curl --location --request DELETE 'https://azure-movies-api.azurewebsites.net/api/v1/movies/1'
property | type | |
---|---|---|
movie-id | integer | required |
200 - OK
{
"id": 1
}
GET /v1/actors
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/actors'
200 - OK
[
{
"id": 6,
"first-name": "{first-name}",
"last-name": "{last-name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6"
}
},
{
"roles": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6/roles"
}
}
]
},
{
"id": 7,
"first-name": "{first-name}",
"last-name": "{last-name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/7"
}
},
{
"roles": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/7/roles"
}
}
]
}
]
GET /v1/actors/{actor-id}
$ curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/actors/6'
property | type | |
---|---|---|
actor-id | integer | required |
200 - OK
{
"id": 6,
"first-name": "{first-name}",
"last-name": "{last-name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6"
}
},
{
"roles": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6/roles"
}
}
]
}
POST /v1/actors
$ curl --location --request POST 'https://azure-movies-api.azurewebsites.net/api/v1/actors' \
--data-raw '{
"first-name": "{first-name}",
"last-name": "{last-name}"
}'
{
"first-name": "{first-name}",
"last-name": "{last-name}"
}
property | type | |
---|---|---|
first-name | string | required |
last-name | string | optional |
201 - Created
{
"id": 1,
"first-name": "{first-name}",
"last-name": "{last-name}",
"created-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/1"
}
}
]
}
PUT /v1/actors/{actor-id}
$ curl --location --request PUT 'https://azure-movies-api.azurewebsites.net/api/v1/actors/15' \
--data-raw '{
"first-name": "{first-name}",
"last-name": "{last-name}"
}'
property | type | |
---|---|---|
actor-id | integer | required |
{
"first-name": "{first-name}",
"last-name": "{last-name}"
}
property | type | |
---|---|---|
first-name | string | required |
last-name | string | optional |
200 - OK
{
"id": 15,
"first-name": "{first-name}",
"last-name": "{last-name}",
"created-at": "{timestamp}",
"updated-at": "{timestamp}",
"_links": [
{
"self": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/15"
}
}
]
}
DELETE /v1/actors/{actor-id}
$ curl --location --request DELETE 'https://azure-movies-api.azurewebsites.net/api/v1/actors/15'
property | type | |
---|---|---|
actor-id | integer | required |
200 - OK
{
"id": 15
}
GET /v1/actors/{actor-id}/roles
curl --location --request GET 'https://azure-movies-api.azurewebsites.net/api/v1/actors/6/roles'
property | type | |
---|---|---|
actor-id | integer | required |
200 - OK
[
{
"id": 10,
"name": "{name}",
"movie-id": 76,
"actor-id": 6,
"_links": [
{
"movie": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/76"
}
},
{
"actor": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6"
}
}
]
}
]
GET /v1/actors/{actor-id}/roles/{movie-id}
$ curl --location --request GET 'http://azure-movies-api.azurewebsites.net/api/v1/actors/6/roles/76'
property | type | |
---|---|---|
actor-id | integer | required |
movie-id | integer | required |
200 - OK
[
{
"id": 10,
"name": "{name}",
"movie-id": 76,
"actor-id": 6,
"_links": [
{
"movie": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/76"
}
},
{
"actor": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/6"
}
}
]
}
]
POST /v1/actors/{actor-id}/roles/{movie-id}
$ curl --location --request POST 'https://azure-movies-api.azurewebsites.net/api/v1/actors/16/roles/77' \
--data-raw '{
"name": "{name}"
}'
property | type | |
---|---|---|
actor-id | integer | required |
movie-id | integer | required |
{
"name": "{name}"
}
property | type | |
---|---|---|
name | string | required |
201 - Created
{
"id": 26,
"name": "{name}",
"movie-id": 77,
"actor-id": 16,
"_links": [
{
"movie": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/77"
}
},
{
"actor": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/16"
}
}
]
}
DELETE /v1/actors/{actor-id}/roles/{movie-id}/{role-id}
$ curl --location --request DELETE 'https://azure-movies-api.azurewebsites.net/api/v1/actors/16/roles/77/26'
property | type | |
---|---|---|
actor-id | integer | required |
movie-id | integer | required |
role-id | integer | required |
200 - OK
{
"movie-id": 77,
"actor-id": 16,
"_links": [
{
"movie": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/movies/77"
}
},
{
"actor": {
"href": "https://azure-movies-api.azurewebsites.net/api/v1/actors/16"
}
}
]
}
email: [email protected] linkedin: https://www.linkedin.com/in/laurin/